简体   繁体   English

在Edit上使用MVC的Entity Framework 4.3不保存复杂对象

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

I made a small project with Northwind database to illustrate the problematic. 我用Northwind数据库做了一个小项目来说明问题。

Here is the action of the controller : 这是控制器的动作:

[HttpPost]
public ActionResult Edit(Product productFromForm)
{
    try
    {
        context.Products.Attach(productFromForm);
        var fromBD = context.Categories.Find(productFromForm.Category.CategoryID);
        productFromForm.Category = fromBD;
        context.Entry(productFromForm).State = EntityState.Modified;
        context.SaveChanges();
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

context is instanced in the constructor of the Controller as new DatabaseContext() . context作为new DatabaseContext()在Controller的构造函数中实例化。

public class DatabaseContext:DbContext
{
    public DatabaseContext()
        : base("ApplicationServices") {
        base.Configuration.ProxyCreationEnabled = false;
        base.Configuration.LazyLoadingEnabled = false;
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder){

        modelBuilder.Configurations.Add(new ProductConfiguration());
        modelBuilder.Configurations.Add(new CategoriesConfiguration());
    }

    private class ProductConfiguration : EntityTypeConfiguration<Product> {
        public ProductConfiguration() {
            ToTable("Products");
            HasKey(p => p.ProductID);
            HasOptional(p => p.Category).WithMany(x=>x.Products).Map(c => c.MapKey("CategoryID"));
            Property(p => p.UnitPrice).HasColumnType("Money");
        }
    }

    private class CategoriesConfiguration : EntityTypeConfiguration<Category> {
        public CategoriesConfiguration() {
            ToTable("Categories");
            HasKey(p => p.CategoryID);
        }
    }
}

public class Category {
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

public class Product {
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public string QuantityPerUnit { get; set; }
    public decimal UnitPrice { get; set; }
    public Int16 UnitsInStock { get; set; }
    public Int16 UnitsOnOrder { get; set; }
    public Int16 ReorderLevel { get; set; }
    public bool Discontinued { get; set; }
    public virtual Category Category { get; set; }
}

The problem is that I can save anything from the Product but not the change of the category. 问题是我可以保存产品中的任何内容,但不能保存类别的更改。

The object productFromForm contains the new CategoryID inside productFromForm.Product.ProductID without problem. 对象productFromForm在productFromForm.Product.ProductID中包含新的CategoryID而没有问题。 But, when I Find() the category to retrieve the object from the context I have an object without Name and Description (both stay to NULL) and the SaveChanges() doesn't modify the reference even if the ID has changed for the property Category . 但是,当我Find()从上下文中检索对象的类别时,我有一个没有Name和Description的对象(都保持为NULL)并且SaveChanges()不会修改引用,即使该属性的ID已更改Category

Any idea why? 知道为什么吗?

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. 此行将productFromFormproductFromForm.Category附加到上下文。

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

This line returns the attached object productFromForm.Category , NOT the object from the database. 该行返回附加对象productFromForm.Category ,而不是数据库中的对象。

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. 此行仅影响productFromForm的标量属性,而不影响任何导航属性。

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. 如果在模型中将外键作为属性公开,则会变得容易得多 - 正如@Laniency的答案和前一个问题的答案中所述。 With FK properties (and assuming that you bind Product.CategoryID directly to a view and not Product.Category.CategoryID ) the code above reduces to: 使用FK属性(并假设您将Product.CategoryID直接绑定到视图而不是Product.Category.CategoryID ),上面的代码Product.Category.CategoryID为:

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: 或者,您可以将状态设置为Modified ,这将适用于FK属性:

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

The problem is that EF tracks association updates differently than value types. 问题是EF跟踪关联更新的方式与值类型不同。 When you do this, context.Products.Attach(productFromForm); 当你这样做时, context.Products.Attach(productFromForm); , the productFromForm is just a poco that doesn't track any changes. ,productFromForm只是一个不跟踪任何变化的poco。 When you mark it as modified, EF will update all value types, but not associations. 将其标记为已修改时,EF将更新所有值类型,但不更新关联。

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). 对任何输入(类ProductInput)使用DTO。 Then use something like AutoMapper to map the data to your domain object. 然后使用类似AutoMapper的东西将数据映射到域对象。 Especially useful as you start submitting increasingly complicated data. 在开始提交日益复杂的数据时特别有用。
  • Explicitly declare foreign keys in your domain objects. 在域对象中明确声明外键。 Ie, add a CategoryId do your product. 即,添加一个CategoryId做你的产品。 Map your input to this property, not the association object. 将输入映射到此属性,而不是关联对象。 Ladislav's answer and subsequent post explain more on this. Ladislav的回答后续帖子对此进行了更多解释。 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; } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM