EF 4: Using MVC and the repository approach to appropriately update objects in DbContext

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

Question

I encountered a problem while attempting to construct an AuditLog utilizing the DBContext's ChangeTracker object.DbEntityEntry.OriginalValues were being destroyed, and their place was taken by theDbEntityEntry.CurrentValues . It was pointed out to me that the issue was how. I was making updates to the object that the DbContext was tracking (original post: Entity Framework SaveChanges DbContext () OriginalValue Unreliable).

Therefore, I'm in need of advice on how to properly update a persistent object in MVC 3 using Entity Framework 4. This sample code was modified from the Pro Asp.NET MVC 3 Framework book's SportsStore application.

Here is my AdminController "Edit" post action:

[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 invokes the actual EFProductRepository class (which is implementing the IProductRepository interface and injected via Ninject). This is theSaveProduct in the concrete repository class method:

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 issue is that when, as was pointed out to me in my prior SO article,context.Entry(product).State = EntityState.Modified; When called, the ChangeTracker's capacity to report on the modifications is somehow messed up. I am not seeing proper results in the DBContext.SaveChanges(string userID) function because of this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValues object.

IF MY EFPRODUCT REPO IS UPDATED. This works using the SaveProduct method:

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

In order for the ChangeTracker to appropriately monitor my changes to the POCO class in the repository, I need to know how to properly update and persist the Product object in this case. Should I follow the later example (apart from copying over any fields that may have been modified, of course), or should I use a different strategy?

The "Product" class in this example is quite basic and simply includes string and decimal attributes. We'll have "complex" types in our actual program, and the POCO classes will make references to other objects (i.e. Person that has a list of addresses). I am aware that in this scenario, I may also need to take further action to monitor the changes. Maybe now that I know this, some of the advise I get here will be different.

1
19
5/23/2017 12:33:15 PM

Accepted Answer

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

No, nothing is messed up by it. The ability of the change tracker is predicated on the fact that it is aware of the entity before making modifications. However, in your situation, the POCO entity does not retain any information about its initial values, and the change tracker is alerted about the entity with modifications already made. A POCO object only has one set of values that may be viewed as both original and current. You must code it yourself if you want anything else.

Am I supposed to do the latter example

Yes, in your straightforward example, and you may easily 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 issue is that only simple and complicated characteristics benefit from this. Another issue is that because this overwrites all properties, you still need to provide the right current value to your entity's fields if, for example, you don't want to display them in the user interface or allow users to change them.product else the application of current values will overwrite that value.

When you attempt to apply this to a real-world setting, everything becomes like far more complicated. Before you develop a lot of code to cover precisely your circumstances, you will fail and you will fail a lot since there is probably no general answer. EF does not support any of these situations. The reason is because every entity and certain associations in EF have internal state machines, and you must specify the state for each entity or association you wish to update, insert, or remove while adhering to EF internal constraints. Setting the state of an entity will only affect that particular entity and not its relationships.

I just import the current entity and all of its relations from the database before manually combining the whole entity graph in code (simply you have detached and attached entity graph and you must transfer all changes from detached to attached one).

36
5/23/2017 12:15:08 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow