Entity Framework 4.3 with MVC on Edit doesn't save complex object

风格不统一 提交于 2019-12-03 09:03:24

Your (apparently) changed relationship doesn't get saved because you don't really change the relationship:

context.Products.Attach(productFromForm);

This line attaches productFromForm AND productFromForm.Category to the context.

var fromBD = context.Categories.Find(productFromForm.Category.CategoryID);

This line returns the attached object productFromForm.Category, NOT the object from the database.

productFromForm.Category = fromBD;

This line assigns the same object, so it does nothing.

context.Entry(productFromForm).State = EntityState.Modified;

This line only affects the scalar properties of productFromForm, not any navigation properties.

Better approach would be:

// Get original product from DB including category
var fromBD = context.Products
    .Include(p => p.Category)  // necessary because you don't have a FK property
    .Single(p => p.ProductId == productFromForm.ProductId);

// Update scalar properties of product
context.Entry(fromBD).CurrentValues.SetValues(productFromForm);

// Update the Category reference if the CategoryID has been changed in the from
if (productFromForm.Category.CategoryID != fromBD.Category.CategoryID)
{
    context.Categories.Attach(productFromForm.Category);
    fromBD.Category = productFromForm.Category;
}

context.SaveChanges();

It becomes a lot easier if you expose foreign keys as properties in the model - as already said in @Leniency's answer and in the answer to your previous question. With FK properties (and assuming that you bind Product.CategoryID directly to a view and not Product.Category.CategoryID) the code above reduces to:

var fromBD = context.Products
    .Single(p => p.ProductId == productFromForm.ProductId);
context.Entry(fromBD).CurrentValues.SetValues(productFromForm);
context.SaveChanges();

Alternatively you can set the state to Modified which would work with FK properties:

context.Entry(productFromForm).State = EntityState.Modified;
context.SaveChanges();
Leniency

The problem is that EF tracks association updates differently than value types. When you do this, context.Products.Attach(productFromForm);, the productFromForm is just a poco that doesn't track any changes. When you mark it as modified, EF will update all value types, but not associations.

A more common way is to do this:

[HttpPost]
public ActionResult Edit(Product productFromForm)
{
    // Might need this - category might get attached as modified or added
    context.Categories.Attach(productFromForm.Category);

    // This returns a change-tracking proxy if you have that turned on.
    // If not, then changing product.Category will not get tracked...
    var product = context.Products.Find(productFromForm.ProductId);

    // This will attempt to do the model binding and map all the submitted 
    // properties to the tracked entitiy, including the category id.
    if (TryUpdateModel(product))  // Note! Vulnerable to overposting attack.
    {
        context.SaveChanges();
        return RedirectToAction("Index");
    }

    return View();
}

The least-error prone solution I've found, especially as models get more complex, is two fold:

  • Use DTO's for any input (class ProductInput). Then use something like AutoMapper to map the data to your domain object. Especially useful as you start submitting increasingly complicated data.
  • Explicitly declare foreign keys in your domain objects. Ie, add a CategoryId do your product. Map your input to this property, not the association object. Ladislav's answer and subsequent post explain more on this. Both independent associations and foreign keys have their own issues, but so far I've found the foreign key method to have less headaches (ie, associated entities getting marked as added, order of attaching, crossing database concerns before mapping, etc...)

    public class Product
    {
        // EF will automatically assume FooId is the foreign key for Foo.
        // When mapping input, change this one, not the associated object.
        [Required]
        public int CategoryId { get; set; }
    
        public virtual Category Category { get; set; }
    }
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!