Reputation: 445
I have an MVC application with the DBContext created once per request.
Within this request I try to save a list of orders
...
foreach (var order in orders)
{
SaveOrder(order);
}
public SaveOrder(Order order)
{
_context.Orders.Add(order);
_context.SaveChanges();
}
...
However if the first order in the list throws an exception, then none of the rest of them save.
I have tried catching the exception and then using
_context.Orders.Remove(order)
but on the next call to SaveChanges() on the DB context the original exception still fires.
Some articles seem to suggest using a new context for every action, and wrapping in a using statement - however is this not expensive (will it not create a new connection every time?)
What is the correct way to do this? Is there a way to remove all pending changes?
Thanks
Upvotes: 0
Views: 825
Reputation: 34908
To handle the case where you want to remove an item from the context, you need to detach it from the DbContext rather than calling Remove
. Remove
could result in some undesired consequences, such as deleting an existing row.
So in your case, the SaveOrder call should work by doing this:
public void SaveOrder(Order order)
{
try
{
_context.Orders.Add(order);
_context.SaveChanges();
}
catch (DbUpdateException ex)
{
_context.Entry(order).State = EntityState.Detached; // De-associate from the poisoned order.
// TODO: Consider logging that this order could not be saved or returning a result to that effect.
}
}
Note that if and Order entity has children that were added/updated as part of this operation, you will need to detach these as well. I.e.
catch (DbUpdateException ex)
{
_context.Entry(order).State = EntityState.Detached;
foreach(var orderLine in order.OrderLines)
{
_context.Entry(orderLine).State = EntityState.Detached;
}
}
Only for rows that were added or otherwise updated by the operation. You wouldn't want to detach Order.Customer if the customer record was not created/updated by this operation.
I prefer to use unit of work patterns which would see inserting a new order as a single operation, comprising a unit of work. If I create 5 orders, each is independent of each other (1 failing does not affect the others) then they are 5 separate operations. Spinning up a scoped DbContext is usually the safest option to avoid cross-operation "poisoning" and does not incur a significant cost. For operations that touch the same data there is the extra read costs, though these are typically fetch by ID scenarios (Customer By Id, Products By Id, etc.) so they are low cost. Messing too much with attached and detached state around orders can lead to more significant problems as after a Save call, the "order" reference may, or may not now be a tracked entity.
Upvotes: 3
Reputation: 406
May be this would help
public void SaveOrders()
{
try
{
foreach (var order in orders)
{
if (!IsOrderEntryValid(order))
{
_invalidOrders.Add(order);
continue;
}
if(!_context.Orders.Exists(/* write your logic to check if the order exists*/))
_context.Orders.Add(order);
}
_context.SaveChanges();
}
catch(Exception ex)
{
// log the exception
}
}
public ValidateOrder(Order order)
{
// do some validations here that would not violate the data format and rules, cause violations and exceptions.
}
This is very generic because of the given information from the question. I am sure, you'd figure out the way to validate and ensure saving the data doesnt throw exceptions because of db violations, constraint violations, duplicates etc. You would have learnt from the exception it has thrown and/or must log it. To me, an evolving system involves learning of the platform and its behavior with respect to the data.
Upvotes: 1