我已经为此感到沮丧了一天......请帮忙。
我有一个发票部分,在其中我可以通过使用 JQuery 附加/删除 DOM 元素来动态添加新项目...我已经弄清楚如何正确命名这些元素,以便它们能够正确地映射到模型并发送到操作参数。
假设我的 Controller 中有一个名为 Edit 的操作,其参数类型为 Invoice
[HttpPost]
public virtual ActionResult Edit(Invoice invoice) {
if (ModelState.IsValid) {
//code discussed below
}
return RedirectToAction("Index");
}
发票参数包含我想要的所有数据。我对此没有问题。以下是对象模型。
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; }
}
注意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; }
}
这就是我陷入困境的地方。
我无论如何都无法将发票集合 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();
解决方案 #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);
}
解决方案 #3 - 根据 http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx 将现有但已修改的实体附加到上下文
db.Entry(invoice).State = EntityState.Modified;
db.SaveChanges();
<小时/>
解决方案 #1 的工作原理:更新除新添加的 InvoiceItems 之外的所有属性。 SetValues() 忽略 ICollection 属性。如果我在 db.SaveChanges(); 之前添加此行,这将有效
origEntry.Entity.InvoiceItems = invoice.InvoiceItems;
...但我根本不认为这是正确的方法。
解决方案 #2 的工作原理:根本不起作用,因为 DbContext 类中没有 ObjectContext 属性。
解决方案 #3 的工作原理:此解决方案将更新发票,但会忽略 InvoiceItems。
<小时/>其他信息:以下是用于生成映射到 InvoiceItem 属性的项目元素的 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" />',
});
最后一个问题:我做错了什么?将与另一个模型相关的新项目集合自动插入数据库有什么问题吗?当父模型更新成功时,为什么它被忽略?
编辑 1:更正
最佳答案
DbEntityEntry.CurrentValues.SetValues
仅更新标量和复杂属性。它不关心导航属性。这就是 InvoiceItems
集合中的更改不会写入数据库的原因。将 Invoice
的状态设置为 Modified
也会出现同样的问题。这仅将标量和复杂属性标记为已修改,但不标记任何导航属性。
不幸的是,EF 没有提供一种简单的方法来分析导航集合中的哪些项目已添加、删除或仅与数据库中的原始状态相比进行了修改(如果在实体分离时进行了更改)来自上下文(在您的示例中的网页上)。
这意味着您必须自己对数据库中的原始状态和新更改的状态进行比较。
如何执行此操作的代码示例位于此处的答案中,例如:The relationship could not be changed because one or more of the foreign-key properties is non-nullable这些示例非常适合您的情况,只需将 ParentItem
替换为 Invoice
,将 ChildItems
替换为 InvoiceItems
。
您还可以查看几天前的这个问题以获取更多信息:WCF, Entity Framework 4.1 and Entity's State实际上还有许多其他问题与同样的问题有关,因为缺乏对更新分离对象图的支持(这是一种相当常见的情况)是 Entity Framework 更令人不快的限制之一。
(注意:在 MVC Controller 中,有 UpdateModel
方法。我不确定此方法是否能够更新完整的对象图(添加、删除和更改集合中的项目) )。就我个人而言,我不使用它,如果我要使用它,那么它不是用于处理数据库模型,而是用于 ViewModel 更新。)
关于ASP.NET MVC 3 EF CodeFirst - DBContext 项编辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7050235/