C# EF 4:如何使用具有存储库模式的 MVC 正确更新 DbContext 中的对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9591165/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 08:02:18  来源:igfitidea点击:

EF 4: How to properly update object in DbContext using MVC with repository pattern

c#asp.net-mvc-3entity-framework

提问by Joe DePung

I'm trying to implement an AuditLog using the DBContext's ChangeTracker object, I ran into an issue where the DbEntityEntry.OriginalValueswere getting wiped out and replaced with the DbEntityEntry.CurrentValues. It was brought to my attention that the problem was howI was updating the object that was being tracked in the DbContext (original post: Entity Framework DbContext SaveChanges() OriginalValue Incorrect).

我正在尝试使用 DBContext 的 ChangeTracker 对象实现 AuditLog,但遇到了一个问题,DbEntityEntry.OriginalValuesDbEntityEntry.CurrentValues. 我注意到问题在于如何更新在 DbContext 中被跟踪的对象(原帖:Entity Framework DbContext SaveChanges() OriginalValue Incorrect)。

So now I need some help on the proper way to update a persisted object using the repository pattern in MVC 3 with Entity Framework 4. This example code is adapted from the SportsStore application in the Pro Asp.NET MVC 3 Framework book put out by Apress.

所以现在我需要一些关于使用实体框架 4 的 MVC 3 中的存储库模式更新持久对象的正确方法的帮助。此示例代码改编自 Apress 出版的 Pro Asp.NET MVC 3 Framework 书中的 SportsStore 应用程序.

Here is my 'Edit' post action in the AdminController:

这是我在 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);
    }
}

This calls into concrete class EFProductRepository (which is implementing the IProductRepository interface and injected via Ninject). Here is the SaveProductmethod in the concrete repository class:

这会调用具体类 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);
}

The problem (as was brought to my attention in my previous SO post), is that when context.Entry(product).State = EntityState.Modified;is called it somehow messes up the ChangeTracker's ability to report on the changes. So in my overloaded DBContext.SaveChanges(string userID) method, I am not seeing accurate values in the ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValuesobject.

问题(在我之前的 SO 帖子中引起了我的注意)是,何时context.Entry(product).State = EntityState.Modified;调用它会以某种方式扰乱 ChangeTracker 报告更改的能力。所以在我重载的 DBContext.SaveChanges(string userID) 方法中,我没有在ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValues对象中看到准确的值。

If I update my EFProductRepository.SaveProduct method to this it works:

如果我将我的 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);
}

I would like to know the proper way to update the Product object and persist it in this scenario in such a way that the ChangeTracker accurately tracks my changes to the POCO class in the repository. Am I supposed to do the latter example (except of course copying over all fields that may have been updated), or should I be taking a different approach?

我想知道更新 Product 对象的正确方法并将其保留在这种情况下,以便 ChangeTracker 准确地跟踪我对存储库中 POCO 类的更改。我应该做后一个例子(当然除了复制可能已更新的所有字段),还是应该采取不同的方法?

In this example the "Product" class is very simple and only has string properties and decimal properties. In our real application we will have "complex" types and the POCO classes will reference other objects (i.e. Person that has a list of addresses). I know I may also need to do something special to track the changes in this case. Perhaps knowledge of this will change some advice that I receive here.

在这个例子中,“Product”类非常简单,只有字符串属性和小数属性。在我们的实际应用程序中,我们将拥有“复杂”类型,而 POCO 类将引用其他对象(即具有地址列表的 Person)。我知道我可能还需要做一些特殊的事情来跟踪这种情况下的变化。也许这方面的知识会改变我在这里收到的一些建议。

采纳答案by Ladislav Mrnka

it somehow messes up the ChangeTracker's ability to report on the changes

它以某种方式扰乱了 ChangeTracker 报告更改的能力

No it doesn't messes anything. Change tracker ability is based on the fact that change tracker knows the entity prior to making changes. But in your case the change tracker is informed about the entity with changes already applied and POCO entity doesn't keep any information about its original values. POCO entity has only single set of values which is interpreted as both current and original. If you want anything else you must code it yourselves.

不,它不会弄乱任何东西。更改跟踪器能力基于这样一个事实,即更改跟踪器在进行更改之前知道实体。但是在您的情况下,更改跟踪器会获知已应用更改的实体,而 POCO 实体不保留有关其原始值的任何信息。POCO 实体只有一组被解释为当前和原始值的值。如果你想要其他任何东西,你必须自己编码。

Am I supposed to do the latter example

我应该做后一个例子吗

In your simple case yes and you can simply use:

在您的简单情况下,是的,您可以简单地使用:

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);
}

The problem is that this works only for simple and complex properties. Another problem is that this overwrites all properties so if for example your entity has some field you don't want to show in UI (or don't want to let user to edit the field) you must still set correct current value to your productinstance otherwise that value will be overwritten when applying current values.

问题是这仅适用于简单和复杂的属性。另一个问题是这会覆盖所有属性,因此,例如,如果您的实体有一些您不想在 UI 中显示的字段(或不想让用户编辑该字段),您仍然必须为您的product实例设置正确的当前值否则在应用当前值时该值将被覆盖。

The whole situation becomes significantly more complexonce you try to apply this to the real scenario. You will fail and you will fail many times before you write a lot of code to support exactly your cases because there is probably no generic solution EF has no supporting methods for this scenarios. The reason is that EF has internal state machine for every entity and some associations and you must configure the state for every single entity or association you want to update, insert or delete and you must do it in compliance with EF internal rules. Setting state of the entity will change the state of that single entity but not its relations.

一旦您尝试将其应用于实际场景,整个情况就会变得更加复杂。在编写大量代码来完全支持您的案例之前,您将失败并且会失败很多次,因为可能没有通用解决方案 EF 没有支持这种情况的方法。原因是 EF 对每个实体和某些关联都有内部状态机,您必须为要更新、插入或删除的每个实体或关联配置状态,并且必须按照 EF 内部规则进行操作。设置实体的状态将改变单个实体的状态,但不会改变其关系。

I do it simply by loading current entity with all relations from database and manually (in code) merging whole entity graph (simply you have detached and attached entity graph and you must transfer all changes from detached to attached one).

我只是通过从数据库中加载具有所有关系的当前实体并手动(在代码中)合并整个实体图(简单地您已经分离和附加实体图并且您必须将所有更改从分离转移到附加实体)来做到这一点。