1

In my code, I am pulling objects to essentially build a parent object. However, part of this parent object will contain a property that is a list of the child objects used to build the parent object. See the following:

public class Order
{
    public long Id { get; set; }
    public long RequestId{ get; set; } 
    public virtual Request Request { get; set; }
    public double? Lbs { get; set; }
    public string Name { get; set; }
    public virtual ICollection<OrderDetail> OrderDetails{ get; set; } 
}

public class OrderDetail
    {
        public long Id { get; set; }
        public string OrderDetailId { get; set; }
        public long? RequestId { get; set; }
        public virtual Request Request { get; set; }
    }

I am building an Order object from a list of OrderDetail objects, and the list of objects are a virtual part of my Order object. However, when I attempt to run SaveChanges() to my Order object, I get primary key errors from trying to insert the collection of OrderDetails that are a property on the Order object. They are already there! How can I stop this?

  • It's unclear (to me, at least) how you can have a child in your database when the parent has not yet been created. Why is that occurring? It seems backwards. – devlin carnate Oct 15 '19 at 16:19
  • Sorry, let me clarify. CINO: Child in Name Only. I'm only saying that to demonstrate that there are a list of OrderDetail objects in an Order object. – NICO BHHC Coder Oct 15 '19 at 16:23
  • Does this sound like it would work in your situation? https://stackoverflow.com/questions/25441027/how-do-i-stop-entity-framework-from-trying-to-save-insert-child-objects – devlin carnate Oct 15 '19 at 16:31
  • Show how your creating the order object and adding details. – mxmissile Oct 15 '19 at 17:59

1 Answers1

0

Ensure that the OrderDetails are associated with the context that the order is being created. The typical cause is the order details are loaded by a different context, perhaps de-serialized from a web request and associated to the new order. The problem is that the context instance saving the orders does not recognize those OrderDetails and treats them as new (added) entities.

Given an example method like this:

public Order CreateOrder(IEnumerable<OrderDetail> orderDetails)
{
    using (var context = new AppContext())
    {
       var order = new Order();
       order.OrderDetails.AddRange(orderDetails);
       context.Orders.Add(order);
       context.SaveChanges();
       return order;
    }
}

The error occurs because context above does not know about the OrderDetails. Adding the objects to the new order are treated like adding new OrderDetails leading to PK violations or inserting duplicates with new IDs if PKs are generated.

Option 1: When creating a new entity, be sure to load all associated entities Rather than passing entities for associations, you can save bandwidth by passing IDs:

public Order CreateOrder(IEnumerable<int> orderDetailIds)
{
    using (var context = new AppContext())
    {
       var order = new Order();
       var orderDetails = context.OrderDetails.Where(x => orderDetailIds.Contains(x.OrderDetailId)).ToList();
       order.OrderDetails.AddRange(orderDetails);
       context.Orders.Add(order);
       context.SaveChanges();
       return order;
    }
}

This ensures the order details are known by the context, and associated to the new order rather than treated as new records.

Option 2: Associate entities to the context

public Order CreateOrder(IEnumerable<OrderDetail> orderDetails)
{
    using (var context = new AppContext())
    {
       var order = new Order();
       foreach(var orderDetail in orderDetails)
       {
           context.OrderDetails.Attach(orderDetail);
       }
       order.OrderDetails.AddRange(orderDetails);
       context.Orders.Add(order);
       context.SaveChanges();
       return order;
    }
}

This option associates the order details with the context by attaching them. This can work ok when the DbContext is scoped to the method, and assuming all references being attached are unique (no double-ups). For longer-lived calls you need to be careful if there is a possibility of an entity already having been attached or otherwise loaded by the context where the Attach call can fail. Attaching entities coming from a web client should also not be trusted, so you should ensure that the entity state never gets set to modified or this opens the door for tampered data being persisted with a SaveChanges call.

There are hybrid options that can check the local cache for references and create stubs which I was about to outline but these are really only suited to bulk operations and come with significant risks.

Steve Py
  • 26,149
  • 3
  • 25
  • 43