简体   繁体   English

将updatemodel与EF4.3.1一起使用时出现InvalidOperationException

[英]InvalidOperationException when using updatemodel with EF4.3.1

When I update my model I get an error on a child relation which I also try to update. 当我更新我的模型时,我得到一个关于子关系的错误,我也尝试更新。

My model, say Order has a releationship with OrderItem. 我的模型,说Order与OrderItem有关系。 In my view I have the details of the order together with an editortemplate for the orderitems. 在我看来,我有订单的详细信息以及orderiteemplate for orderitems。 When I update the data the link to Order is null but the orderid is filled, so it should be able to link it, TryUpdateModel returns true, the save however fails with: 当我更新数据时,Order的链接为null,但orderid已填充,因此它应该能够链接它,TryUpdateModel返回true,但保存失败,但是:

InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. InvalidOperationException:操作失败:无法更改关系,因为一个或多个外键属性不可为空。 When a change is made to a relationship, the related foreign-key property is set to a null value. 当对关系进行更改时,相关的外键属性将设置为空值。 If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.] 如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

My update method: 我的更新方法:

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

I tried all solutions I saw on SO and other sources, none succeeded. 我尝试了在SO和其他来源上看到的所有解决方案,没有成功。

I use .Net MVC 3, EF 4.3.1 together with DBContext. 我使用.Net MVC 3,EF 4.3.1和DBContext。

There are a number of code smells here, which I'll try to be elegant with when correcting :) 这里有许多代码味道,我会在纠正时尝试优雅:)

I can only assume that "Order" is your EF entity? 我只能假设“订单”是你的EF实体? If so, I would highly recommend keeping it separate from the view by creating a view model for your form and copying the data in to it. 如果是这样,我强烈建议您通过为表单创建视图模型并将数据复制到视图中,将其与视图分开。 Your view model should really only contain properties that your form will be using or manipulating. 您的视图模型应该只包含表单将使用或操作的属性。

I also presume orderRepository.GetOrder() is a data layer call that retrieves an order from a data store? 我还假设orderRepository.GetOrder()是一个从数据存储中检索订单的数据层调用?

You are also declaring potentially unused variables. 您还声明了可能未使用的变量。 " var order = " will be loaded even if your model is invalid, and " var success = " is never used. 即使您的模型无效,也会加载“ var order = ”,并且永远不会使用“ var success = ”。

TryUpdateModel and UpdateModel aren't very robust for real-world programming. TryUpdateModel和UpdateModel对于实际编程来说不是很强大。 I'm not entirely convinced they should be there at all, if I'm honest. 如果我诚实的话,我并不完全相信他们应该在那里。 I generally use a more abstracted approach, such as the service / factory pattern. 我通常使用更抽象的方法,例如服务/工厂模式。 It's more work, but gives you a lot more control. 这是更多的工作,但给你更多的控制。

In your case, I would recommend the following pattern. 在您的情况下,我会建议以下模式。 There's minimal abstraction, but it still gives you more control than using 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);
    }

Not ideal, but it should serve you a bit better... 不理想,但它应该为你提供更好的服务......

I refer you to this post, which is similar. 我推荐你这篇文章,这是类似的。

I assume that the user can perform the following actions in your view: 我假设用户可以在您的视图中执行以下操作:

  1. Modify order (header) data 修改订单(标题)数据
  2. Delete an existing order item 删除现有订单商品
  3. Modify order item data 修改订单商品数据
  4. Add a new order item 添加新订单商品

To do a correct update of the changed object graph (order + list of order items) you need to deal with all four cases. 要正确更新已更改的对象图(订单+订单商品列表),您需要处理所有四种情况。 TryUpdateModel won't be able to perform a correct update of the object graph in the database. TryUpdateModel将无法在数据库中执行对象图的正确更新。

I write the following code directly using a context . 我使用context直接编写以下代码。 You can abstract the use of the context away into your repository. 您可以将上下文的使用抽象到您的存储库中。 Make sure that you use the same context instance in every repository that is involved in the following code. 确保在以下代码中涉及的每个存储库中使用相同的上下文实例。

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

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

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