当我更新我的模型时,我得到一个关于子关系的错误,我也尝试更新。
我的模型,说Order与OrderItem有关系。在我看来,我有订单的详细信息以及orderiteemplate for orderitems。当我更新数据时,Order的链接为null,但orderid已填充,因此它应该能够链接它,TryUpdateModel返回true,但保存失败但是:
InvalidOperationException:操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。]
我的更新方法:
public ActionResult ChangeOrder(Order model)
{
var order = this.orderRepository.GetOrder(model.OrderId);
if (ModelState.IsValid)
{
var success = this.TryUpdateModel(order);
}
this.orderRepository.Save();
return this.View(order);
}
我尝试了在SO和其他来源上看到的所有解决方案,没有成功。
我使用.Net MVC 3,EF 4.3.1和DBContext。
答案 0 :(得分:4)
这里有许多代码味道,我会在纠正时尝试优雅:)
我只能假设“订单”是您的EF实体?如果是这样,我强烈建议您通过为表单创建视图模型并将数据复制到视图中,将其与视图分开。您的视图模型应该只包含表单将使用或操作的属性。
我还假设orderRepository.GetOrder()是一个从数据存储中检索订单的数据层调用吗?
您还声明可能未使用的变量。即使您的模型无效,也会加载“var order =
”,并且永远不会使用“var success =
”。
TryUpdateModel和UpdateModel对于实际编程来说不是很强大。如果我诚实的话,我并不完全相信他们应该在那里。我通常使用更抽象的方法,例如服务/工厂模式。这是更多的工作,但给你更多的控制。
在您的情况下,我会推荐以下模式。这是最小的抽象,但它仍然比使用TryUpdateModel / UpdateModel提供更多控制:
public ActionResult ChangeOrder(OrderViewModel model) {
if(ModelState.IsValid) {
// Retrieve original order
var order = orderRepository.GetOrder(model.OrderId);
// Update primitive properties
order.Property1 = model.Property1;
order.Property2 = model.Property2;
order.Property3 = model.Property3;
order.Property4 = model.Property4;
// Update collections manually
order.Collection1 = model.Collection1.Select(x => new Collection1Item {
Prop1 = x.Prop1,
Prop2 = x.Prop2
});
try {
// Save to repository
orderRepository.SaveOrder(order);
} catch (Exception ex) {
ModelState.AddModelError("", ex.Message);
return View(model);
}
return RedirectToAction("SuccessAction");
}
return View(model);
}
不理想,但它应该会为你提供更好的服务......
我推荐你this帖子,这是类似的。
答案 1 :(得分:3)
我假设用户可以在您的视图中执行以下操作:
要正确更新已更改的对象图(订单+订单商品列表),您需要处理所有四种情况。 TryUpdateModel
将无法在数据库中执行对象图的正确更新。
我使用context
直接编写以下代码。您可以将上下文的使用抽象到您的存储库中。确保在以下代码中涉及的每个存储库中使用相同的上下文实例。
public ActionResult ChangeOrder(Order model)
{
if (ModelState.IsValid)
{
// load the order from DB INCLUDING the current order items in the DB
var orderInDB = context.Orders.Include(o => o.OrderItems)
.Single(o => o.OrderId == model.OrderId);
// (1) Update modified order header properties
context.Entry(orderInDB).CurrentValues.SetValues(model);
// (2) Delete the order items from the DB
// that have been removed in the view
foreach (var item in orderInDB.OrderItems.ToList())
{
if (!model.OrderItems.Any(oi => oi.OrderItemId == item.OrderItemId))
context.OrderItems.Remove(item);
// Omitting this call "Remove from context/DB" causes
// the exception you are having
}
foreach (var item in model.OrderItems)
{
var orderItem = orderInDB.OrderItems
.SingleOrDefault(oi => oi.OrderItemId == item.OrderItemId);
if (orderItem != null)
{
// (3) Existing order item: Update modified item properties
context.Entry(orderItem).CurrentValues.SetValues(item);
}
else
{
// (4) New order item: Add it
orderInDB.OrderItems.Add(item);
}
}
context.SaveChanges();
return RedirectToAction("Index"); // or some other view
}
return View(model);
}