EF 4: Как правильно обновить объект в DbContext, используя MVC с шаблоном хранилища - PullRequest
19 голосов
/ 07 марта 2012

Я пытаюсь реализовать AuditLog, используя объект ChangeTracker DBContext, я столкнулся с проблемой, когда DbEntityEntry.OriginalValues стирались и заменялись DbEntityEntry.CurrentValues. Мне стало известно, что проблема была как Я обновлял объект, который отслеживался в DbContext (оригинальное сообщение: Entity Framework DbContext SaveChanges () OriginalValue Inclusive ).

Так что теперь мне нужна помощь в правильном способе обновления персистентного объекта с использованием шаблона репозитория в MVC 3 с Entity Framework 4. Этот пример кода адаптирован из приложения SportsStore в книге Pro Asp.NET MVC 3 Framework. вне Apress.

Вот мое действие «Редактировать» в AdminController:

[HttpPost]
public ActionResult Edit(Product product)
{
    if (ModelState.IsValid)
    {
        // Here is the line of code of interest...
        repository.SaveProduct(product, User.Identity.Name);

        TempData["message"] = string.Format("{0} has been saved", product.Name);
        return RedirectToAction("Index");
    }
    else
    {
        // there is something wrong with the data values
        return View(product);
    }
}

Это вызывает конкретный класс EFProductRepository (который реализует интерфейс IProductRepository и внедряется через Ninject). Вот метод SaveProduct в конкретном классе хранилища:

public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        context.Entry(product).State = EntityState.Modified;
    }
    context.SaveChanges(userID);
}

Проблема (на которую я обратил внимание в моем предыдущем посте SO) заключается в том, что когда вызывается context.Entry(product).State = EntityState.Modified;, это как-то портит способность ChangeTracker сообщать об изменениях. Поэтому в моем перегруженном методе DBContext.SaveChanges (string userID) я не вижу точных значений в объекте ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValues.

Если я обновлю свой метод EFProductRepository.SaveProduct, он будет работать:

public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        Product prodToUpdate = context.Products
          .Where(p => p.ProductID == product.ProductID).FirstOrDefault();

        if (prodToUpdate != null)
        {
            // Just updating one property to demonstrate....
            prodToUpdate.Name = product.Name;
        }
    }
    context.SaveChanges(userID);
}

Я хотел бы знать, как правильно обновить объект Product и сохранить его в этом сценарии таким образом, чтобы ChangeTracker точно отслеживал мои изменения в классе POCO в хранилище. Должен ли я сделать последний пример (кроме, конечно, копирования всех полей, которые могли быть обновлены), или я должен использовать другой подход?

В этом примере класс «Product» очень прост и имеет только строковые свойства и десятичные свойства. В нашем реальном приложении у нас будут «сложные» типы, а классы POCO будут ссылаться на другие объекты (т.е. на человека, у которого есть список адресов). Я знаю, что мне также может понадобиться сделать что-то особенное, чтобы отслеживать изменения в этом случае. Возможно, знание этого изменит некоторые советы, которые я получу здесь.

1 Ответ

35 голосов
/ 07 марта 2012

это как-то портит способность ChangeTracker сообщать об изменениях

Нет, это ничего не портит. Возможность отслеживания изменений основана на том факте, что отслеживатель изменений знает объект до внесения изменений. Но в вашем случае средство отслеживания изменений информируется об объекте с уже примененными изменениями, и объект POCO не сохраняет никакой информации о своих первоначальных значениях. Объект POCO имеет только один набор значений, который интерпретируется как текущий и оригинальный. Если вы хотите что-то еще, вы должны написать это сами.

Должен ли я сделать последний пример

В вашем простом случае да, и вы можете просто использовать:

public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        Product prodToUpdate = context.Products
          .Where(p => p.ProductID == product.ProductID).FirstOrDefault();

        if (prodToUpdate != null)
        {
            context.Entry(prodToUpdate).CurrentValues.SetValues(product);
        }
    }

    context.SaveChanges(userID);
}

Проблема в том, что это работает только для простых и сложных свойств. Другая проблема заключается в том, что это перезаписывает все свойства, поэтому, если, например, у вашей сущности есть поле, которое вы не хотите показывать в пользовательском интерфейсе (или не хотите, чтобы пользователь мог редактировать поле), вы все равно должны установить правильное текущее значение для вашего product в противном случае это значение будет перезаписано при применении текущих значений.

Вся ситуация становится значительно более сложной , как только вы попытаетесь применить это к реальному сценарию. Вы потерпите неудачу, и вы будете терпеть неудачу много раз, прежде чем писать много кода для поддержки именно ваших случаев, потому что, вероятно, нет универсального решения. В EF нет методов поддержки для этих сценариев. Причина в том, что EF имеет внутренний конечный автомат для каждой сущности и некоторых ассоциаций, и вы должны настроить состояние для каждой отдельной сущности или ассоциации, которую вы хотите обновить, вставить или удалить, и вы должны сделать это в соответствии с внутренними правилами EF. Установка состояния объекта изменит состояние этого отдельного объекта, но не его отношения.

Я делаю это просто путем загрузки текущей сущности со всеми связями из базы данных и ручного (в коде) слияния всего графа сущностей (просто у вас отсоединен и присоединен граф сущностей, и вы должны перенести все изменения из отсоединенного в присоединенный).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...