【问题标题】:Entity Framework 4.3 with MVC on Edit doesn't save complex object实体框架 4.3 与 MVC on Edit 不保存复杂对象
【发布时间】:2012-03-26 15:50:59
【问题描述】:

我用 Northwind 数据库做了一个小项目来说明问题。

这是控制器的动作:

[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 在 Controller 的构造函数中被实例化为new DatabaseContext()

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

问题是我可以保存产品中的任何内容,但不能保存类别的更改。

对象 productFromForm 包含 productFromForm.Product.ProductID 内的新 CategoryID 没有问题。但是,当我 Find() 从上下文中检索对象的类别时,我有一个没有名称和描述的对象(都保持为 NULL),即使属性的 ID 已更改,SaveChanges() 也不会修改引用Category.

知道为什么吗?

【问题讨论】:

  • 如果您只设置 product.CategoryID 而不是 Category 导航参考会发生什么?
  • productFromForm 已经包含从表单正确填写的 .CategoryID。
  • 啊,我没有直接在控制器中使用我的实体,所以我不确定。

标签: c# asp.net-mvc asp.net-mvc-3 entity-framework entity-framework-4.3


【解决方案1】:

您(显然)更改的关系不会保存,因为您并没有真正改变关系:

context.Products.Attach(productFromForm);

这一行将productFromForm AND productFromForm.Category 附加到上下文中。

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

此行返回附加对象productFromForm.Category,而不是数据库中的对象。

productFromForm.Category = fromBD;

这一行分配了同一个对象,所以它什么也不做。

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

这一行只影响productFromForm 的标量属性,不影响任何导航属性。

更好的方法是:

// 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();

如果您将外键公开为模型中的属性,这会变得容易得多 - 正如@Leniency 的回答和您上一个问题的回答中已经说过的那样。使用 FK 属性(并假设您将 Product.CategoryID 直接绑定到视图而不是 Product.Category.CategoryID)上面的代码简化为:

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

或者,您可以将状态设置为 Modified,这将与 FK 属性一起使用:

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

【讨论】:

  • 谢谢。目前,我们决定不使用外键等数据库内容更改模型。我知道编写的代码更少,但目前这是集体决定。该解决方案运行良好,并且解释非常可靠。做得好。您认为将 0..1 关系的类别设置为 NULL 是否适用于这种代码?
  • @PatrickDesjardins:是的,我认为它会起作用。在将引用设置为 null 之前,您只需使用 Include 加载原始文件。
  • 我做了一个测试,它也有效。非常感谢您的回答和质量。
【解决方案2】:

问题在于 EF 跟踪关联更新的方式与值类型不同。当您这样做时,context.Products.Attach(productFromForm);,productFromForm 只是一个不跟踪任何更改的 poco。当您将其标记为已修改时,EF 将更新所有值类型,但不更新关联。

更常见的方法是这样做:

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

我发现的最不容易出错的解决方案,尤其是随着模型变得越来越复杂,有两个方面:

  • 将 DTO 用于任何输入(ProductInput 类)。然后使用 AutoMapper 之类的东西将数据映射到您的域对象。当您开始提交越来越复杂的数据时尤其有用。
  • 在您的域对象中显式声明外键。即,为您的产品添加一个 CategoryId。将您的输入映射到此属性,而不是关联对象。 Ladislav's answersubsequent post 对此进行更多解释。独立关联和外键都有自己的问题,但到目前为止,我发现外键方法不太麻烦(即关联实体被标记为已添加、附加顺序、映射前交叉数据库问题等...... )

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    相关资源
    最近更新 更多