Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding New Child Object Whilst Modifying Existing Children Entity Framework

I've look at some of the answers to similar questions and they don't really seem to fit mine.

I'm trying to incorporate a pattern from Entity Framework: DbContext(page 90) and it doesn't seem to work. The code that I'm using is given below:

[HttpPost]
public ActionResult Edit(Order order)
{
     if (ModelState.IsValid)
     {
         db.Orders.Add(order);
         db.Entry(order).State = EntityState.Modified;

         foreach (var orderDetail in order.OrderDetails)
         {
              if (orderDetail.OrderId == 0)
              {
                  db.Entry(orderDetail).State = EntityState.Added;
              }
              else
              {
                  db.Entry(orderDetail).State = EntityState.Modified;
              }
              // The example order that I'm updating has two child entities
              // so this orderId will be for the third, added one.
              int addedOrderDetailId = order.OrderDetails[2].OrderId;
          }
          db.SaveChanges();
          return RedirectToAction("Index");
     }

     ViewBag.CustomerId = new SelectList(db.Customers, "CustomerId", "CompanyName", order.CustomerId);

     return View(order);

}

I've been running an example where the Order object has two existing OrderDetail objects and I'm attempting to add a third. I included the addedOrderDetailId variable, so that I could add it to the 'Watch' and see when it changed

What I've found is happening is that the OrderId of the added OrderDetail object (which is 0 when the foreach loop is entered) is being updated by entity framework to the OrderId of the Order object. This is happening at after the first iteration through the foreach loop (when the first child entity is having its state changed to modified. This means that all three children are being marked as modified. This is causing SaveChanges() to try to update an entry into the database that doesn't exist.

If anyone else has had this problem, then I would be greatful for any advice as to get around this. I will also have to deal with existing child objects being deleted, but I haven't got around to this yet, so if anyone knows of a pattern for this, that would also be appreciated.

Edit:

After taking Slauma's advice and removing db.Orders.Add(order). I was able to move the call to db.Entry(order).State underneath the foreach loop. This allowed me to loop through the loop and set the state of each OrderDetail object to modified for the existing ones and added for the added one. I then simply had to assign the OrderId of the parent to the OrderId of the child and the update was successful. I've also included the code that I've used to delete child objects during the edit. I'm not sure how efficient this is, but it works. Here is the revised code:

    [HttpPost]
    public ActionResult Edit(Order order)
    {
        if (ModelState.IsValid)
        {
            List<int> previousProductIds = db.OrderDetails
                .Where(ep => ep.OrderId == order.OrderId)
                .Select(ep => ep.ProductId)
                .ToList();

            List<int> currentProductIds = order.OrderDetails
                .Select(o => o.ProductId)
                .ToList();

            List<int> deletedProductIds = previousProductIds
                .Except(currentProductIds).ToList();


            foreach (var deletedProductId in deletedProductIds)
            {
                OrderDetail deletedOrderDetail = db.OrderDetails
                    .Where(od => od.OrderId == order.OrderId && od.ProductId == deletedProductId)
                    .Single();

                db.Entry(deletedOrderDetail).State = EntityState.Deleted;
            }

            foreach (var orderDetail in order.OrderDetails)
            {
                if (orderDetail.OrderId == 0)
                {
                    db.Entry(orderDetail).State = EntityState.Added;
                    orderDetail.OrderId = order.OrderId;
                }
                else
                {
                    db.Entry(orderDetail).State = EntityState.Modified;
                }
            }
            db.Entry(order).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        ViewBag.CustomerId = new SelectList(db.Customers, "CustomerId", "CompanyName", order.CustomerId);
        return View(order);
    }
like image 694
MickySmig Avatar asked Nov 13 '22 07:11

MickySmig


1 Answers

Remove this line from your code:

db.Orders.Add(order);

This will actually put the order including all orderDetails into Added state. Relationship fixup (which happens automatically in Add) will set the OrderId of all OrderDetails to the key of the order. When you enter the loop orderDetail.OrderId is != 0 for all detail items and you always enter the branch which sets the state to Modified. No orderDetail item is in Added state anymore when the loop is finished.

like image 153
Slauma Avatar answered Dec 27 '22 21:12

Slauma