Подтвердить что ты не робот

EF 4: Как правильно обновить объект в DbContext с помощью MVC с шаблоном репозитория

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

Итак, теперь мне нужна помощь по правильному способу обновления сохраненного объекта с использованием шаблона репозитория в MVC 3 с Entity Framework 4. Этот примерный код адаптирован из приложения SportsStore в книге Pro Asp.NET MVC 3 Framework put из 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 в репозитории. Должен ли я использовать последний пример (за исключением, конечно, копирования всех полей, которые могли быть обновлены), или я должен придерживаться другого подхода?

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

4b9b3361

Ответ 1

это как-то испортило способность 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. Состояние установки объекта изменит состояние этого единственного объекта, но не его отношения.

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