ASP.NET MVC 3 EF CodeFirst - DBContext项目编辑

Mir*_*rek 8 asp.net entity-framework ef-code-first asp.net-mvc-3 dbcontext

我一直对此感到沮丧...请帮忙.

我有一个发票部分,我可以通过使用JQuery附加/删除DOM元素来动态添加新项目...我已经找到了如何正确命名这些元素,因此它们因此正确地映射到模型并发送到action参数.

所以,假设我的控制器中有一个名为Edit with Invram类型为Invoice的动作

[HttpPost]
public virtual ActionResult Edit(Invoice invoice) {
    if (ModelState.IsValid) {
         //code discussed below
    }
    return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)

invoice参数包含我想要的所有数据.我对此没有任何问题.以下是对象模型.

public class Invoice
{
    [Key]
    public int ID { get; set; }
    public InvoiceType InvoiceType { get; set; }
    public string Description { get; set; }
    public int Amount { get; set; }
    public virtual ICollection<InvoiceItem> InvoiceItems { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

请注意InvoiceItems集合 - 它包含动态插入的发票项目的集合.以下是InvoiceItem模型

[Table("InvoiceItems")]
public class InvoiceItem
{
    [Key]
    public int ID { get; set; }

    [ForeignKey("Invoice")]
    public int InvoiceID { get; set; }
    public Invoice Invoice { get; set; }

    public string Description { get; set; }
    public int Amount { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这就是我被困的地方.

我无法通过任何机会将Invoice集合InvoiceItems中的项目插入/更新到数据库中.我一直在谷歌搜索,我发现了这些解决方案 - 不幸的是,这些都没有.

解决方案#1 - 使用SetValues的一些奇怪的代码:

Invoice origInvoice = db.Invoices.Single(x => x.ID == invoice.ID);
var entry = db.Entry(origInvoice);
entry.OriginalValues.SetValues(invoice);
entry.CurrentValues.SetValues(invoice);
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

解决方案#2 - 为我的DbContext添加一个新的更新方法:

public void Update<T>(T entity) where T : class
{
   this.Set<T>().Attach(entity);
   base.ObjectContext.ObjectStateManager.ChangeObjectState(entity,System.Data.EntityState.Modified);
 }
Run Code Online (Sandbox Code Playgroud)

解决方案#3 - 根据http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part将现有但已修改的实体附加到上下文-4-外接连接和实体,states.aspx

db.Entry(invoice).State = EntityState.Modified;
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

解决方案#1的工作原理:更新除新添加的InvoiceItems之外的所有属性.SetValues()忽略ICollection属性.如果我在db.SaveChanges()之前添加这一行,这将有效.

origEntry.Entity.InvoiceItems = invoice.InvoiceItems;
Run Code Online (Sandbox Code Playgroud)

......但我不认为这是正确的做法.

解决方案#2如何工作:根本不起作用,因为DbContext类中没有ObjectContext属性.

解决方案#3的工作原理:此解决方案将更新发票,但忽略InvoiceItems.


附加信息:以下是用于生成映射到InvoiceItem属性的item元素的javascript.

    window.InvoiceItemIndex++;

    var str = $.tmpl("itemRowTmpl", {
        itemIndexes: '<input type="hidden" name="Invoice.InvoiceItems.Index" value="' + window.InvoiceItemIndex + '"/> \
                      <input type="hidden" name="Invoice.InvoiceItems[' + window.InvoiceItemIndex + '].ID" value="0"/>\
                      <input type="hidden" name="Invoice.InvoiceItems[' + window.InvoiceItemIndex + '].InvoiceID" value="@Model.Invoice.ID"/>',
        itemDescription: '<textarea cols="150" name="Invoice.InvoiceItems[' + window.InvoiceItemIndex + '].Description" rows="2"></textarea>',
        itemAmount: '<input type="text" class="InvoiceItemAmount" style="text-align:right;" name="Invoice.InvoiceItems[' + window.InvoiceItemIndex + '].Amount" />',
    });
Run Code Online (Sandbox Code Playgroud)

最后一个问题:我做错了什么?与另一个模型相关的新项目集合自动插入到数据库中有什么问题?当父模型成功更新时,为什么会被忽略?

编辑1:更正

Sla*_*uma 6

DbEntityEntry.CurrentValues.SetValues仅更新标量和复杂属性.它不关心导航属性.这就是为什么InvoiceItems集合中的更改不会写入数据库的原因.同样的问题是设置Invoiceto 的状态Modified.这仅将标量和复杂属性标记为已修改但未标记任何导航属性.

不幸的是,EF没有提供一种简单的方法,可以分析导航集合中的哪些项目与数据库中的原始状态相比已添加,删除或仅修改,如果在实体与上下文分离时已完成更改(在您的示例中的网页上).

这意味着您必须自己对数据库中的原始状态和新更改的状态进行此比较.

代码示例如何执行此操作在此处的答案中:例如:由于一个或多个外键属性不可为空,因此无法更改关系这些示例非常适合您的情况,只需替换ParentItemby InvoiceChildItemsby InvoiceItems.

您还可以在几天前查看此问题以获取更多信息:WCF,实体框架4.1和实体状态实际上还有许多其他问题与缺少对更新分离对象图的支持相关的问题(这是一个非常常见的场景)是实体框架更令人不快的限制之一.

(注意:在MVC控制器中有UpdateModel方法.我不确定这种方法是否能够更新完整的对象图(添加,删除和更改集合中的项).我个人不使用它,并且如果我使用它然后不使用数据库模型,只为ViewModel更新.)