Currently when adding a product to my cart the Add action of my CartController
is called with the orderEntryDisplayViewModel
(the order line object).
[HttpPost]
public RedirectToRouteResult Add(Cart cart, OrderEntryDisplayViewModel orderLine)
{
if (!ModelState.IsValid)
{ return RedirectToAction("Display", "OrderEntry", new { Product = orderLine.Line.PartNum }); }
CompleteProduct product = null;
orderLine.Line.RequestedShipDate = orderLine.RequestedShipDate;
if (orderLine.Line.NewMyPartNum != null)
{ orderLine.Line.MyPartNum = orderLine.Line.NewMyPartNum; }
try
{
product = _inventoryRepo.FetchByPartNum(orderLine.Line.PartNum, User.Identity.Name);
orderLine.Line.Product = product;
cart.AddItem(orderLine.Line);
//_cartRepo.Save();
}
catch (DbUpdateException e)
{ throw; }
catch
{
ModelState.AddModelError("", "Problem adding part to cart");
return RedirectToAction("Index", new { returnUrl = Url.Action("Index", "OrderEntry") });
}
return RedirectToAction("Index", new { returnUrl = Url.Action("Index", "OrderEntry") });
}
Before it is reached the CartModelBinder
either creates or gets the shopping cart from the session.
public class CartModelBinder : IModelBinder
{
private const string sessionKey = "Cart";
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
CartRepository cartRepo = new CartRepository();
Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
if (cart == null)
{
cart = cartRepo.CreateCart(true);
cartRepo.DetachCart(cart);
controllerContext.HttpContext.Session[sessionKey] = cart;
cartRepo.AttachCart(cart);
}
else
{ cartRepo.AttachCart(cart); }
return cart;
}
}
If no cart currently exists in the session a new one is created though the CartRepository
CreateCart
method which then adds the newly created cart to the context.
public Cart CreateCart(bool saveCart = false)
{
Cart cart = new Cart();
context.Carts.Add(cart);
if (saveCart)
{ context.SaveChanges(); }
return cart;
}
Before I add the cart object to the session I detach it from the context using CartRepository
DetachCart
, add it to the session, then attach it to the context again.
public void DetachCart(Cart cart)
{
((IObjectContextAdapter)context).ObjectContext.Detach(cart);
}
The CartRepository
gets the context from ContextHelper
:
public static class ContextHelper
{
public static InsideIIMAKContext InsideIIMAK
{
get
{
if (HttpContext.Current.Items["InsideIIMAKContext"] == null)
{ HttpContext.Current.Items.Add("InsideIIMAKContext", new InsideIIMAKContext()); }
return (InsideIIMAKContext)HttpContext.Current.Items["InsideIIMAKContext"];
}
}
If CartModelBinder
finds a cart in the session then it attempts to attach the cart through the CartRepsitory
method AttachCart
.
public void AttachCart(Cart cart)
{
context.Carts.Attach(cart);
}
At the end of the Add action in the CartController
I redirect to the Index action to display the cart view. The index action requires the Cart object too so CartModelBinder
is called a second time and this time calls the CartRepository
AttachCart
method. My error occurs at the AttachCart
method
"An entity object cannot be referenced by multiple instances of IEntityChangeTracker."
I've researched this issue a good amount and it doesn't seem that I have a situation where I am adding the cart object to two instances of the context like seems to often be the reason for the error. It almost seems as if the cart object stored in the session is being modified with tracking information so when I try to attach it in the next HTTP request EF thinks it is attached to an active context, but really it's the context from the previous request. I found a forum which suggested I needed to detach the Entity Object before adding it to the session, but I've done that and still have the same issue.
Hopefully I haven't missed anything in this explanation.
ANSWER Thanks to Mark Oreta
public void DetachCart(Cart cart)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Detach(cart.Customer);
foreach (var item in cart.Lines)
{ objectContext.Detach(item); }
objectContext.Detach(cart);
}