简体   繁体   中英

Entity Framework - is excessive use of “EntityState.Unchanged” really needed?

Thanks for taking your time!

I'm currently prototyping a smartphone webstore as a part of learning Entity Framework, I stumbled on a problem where new records in my database would add additional records in related tables. I managed to work around this problem by following this post Prevent Adding New Record on Related Table Entity in Entity Framework .

However, it seems quite tedious to add EntityState.Unchanged for each and every entity whenever I want to add a record, so I'm starting to think I might have made an error.

Please take at the following code that is used for check out:

    public ActionResult CheckOut()
    {
        // Save cart
        Order order = new Order() { Payed = false, Canceled = false };
        db.Orders.Add(order);

        for (int i = 0; i < cartItems.Count; i++)
        {
            cartItems[i].Order = order;
            cartItems[i].OrderId = order.OrderId;

            db.CartItems.Add(cartItems[i]);

            db.Entry(cartItems[i].Item).State = EntityState.Unchanged;
            db.Entry(cartItems[i].Item.Memory).State = EntityState.Unchanged;
            db.Entry(cartItems[i].Item.ScreenSize).State = EntityState.Unchanged;
            db.Entry(cartItems[i].Item.OperatingSystem).State = EntityState.Unchanged;
        }

        db.SaveChanges();
        cartItems.Clear();

        return View();
    }

The method takes whatever items the user has put into the cart and adds them into the database. If I don't set State to EntityState.Unchanged , the referenced entities will be added as new records into their corresponding tables.

For better understanding, here's the Item entity and the CartItem class:

public class Item
{
    public int ItemId { get; set; }
    public int MemoryId { get; set; }
    public int OperatingSystemId { get; set; }
    public int BrandId { get; set; }
    public int ScreenSizeId { get; set; }
    public string Name { get; set; }
    public float Price { get; set; }

    public Memory Memory { get; set; }
    public OperatingSystem OperatingSystem { get; set; }
    public ScreenSize ScreenSize { get; set; }
}

public class CartItem
{
    public int CartItemId { get; set; }
    public int OrderId { get; set; }
    public int ItemId { get; set; }
    public int Quantity { get; set; }

    public Order Order { get; set; }
    public Item Item{ get; set; }
}

What really grinds my gears is that if I combine the code that is used to add items into the cart and the checkout-method, I don't get this problem at all (notice the lack of state = EntityState.Unchanged ) .

    public ActionResult ProblemFreeCheckOut()
    {
        // Add Items to cart...
        var item1= db.Items.Include(i => i.Memory).Include(i => i.OperatingSystem).Include(i => i.ScreenSize).Where(i => i.ItemId == 1);
        var item2= db.Items.Include(i => i.Memory).Include(i => i.OperatingSystem).Include(i => i.ScreenSize).Where(i => i.ItemId== 2);

        CartItems List<CartItems> = new List<CartItem>();

        cartItems.Add(new CartItem { Quantity = 1, ItemId = item1.First().ItemId, Item = item1.First() });
        cartItems.Add(new CartItem { Quantity = 1, ItemId= item2.First().ItemId, Item = item2.First() });

        // Save cart
        Order order = new Order() { Payed = false, Canceled = false };
        db.Orders.Add(order);

        for (int i = 0; i < cartItems.Count; i++)
        {
            cartItems[i].Order = order;
            cartItems[i].OrderId = order.OrderId;
            db.CartItems.Add(cartItems[i]);
        }

        db.SaveChanges();
        cartItems.Clear();

        return View();
    }

To summarize and clarify what I'm trying to do: I want to be able to split the code above into two parts: one Add method and one Checkout method. The Add method will add items from database into the a list of items that will later be utilized by the Checkout method. I have got this working, by an excessive use of EntityState.Unchanged - is this really needed?

Thanks a lot if you got this far in the post, I salute you!

Take a look at the documentation for the Add<TEntity>(TEntity) method:

Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.

So what happens is, by calling the Add method you tell the EF: "treat everything I just attached to the context (via the relation graph) as a new item".
In the second example you don't encounter the problem because the related entities are already being tracked when you Include them. In that case EF simply keeps the Unchanged stated which results in them being ignored during SaveChanges() .

Purely from the technical point of view, you have two options. Either:
1) do what you do in your second example - track the related entities before calling the Add() method so that EF knows to treat them as Unchanged .
or
2) instead of adding the entity graph outright

db.CartItems.Add(cartItem);

just attach it (all the entities will be tracked as Unchanged by default) and then mark your cartItem as added:

db.CartItems.Attach(cartItem).State = EntityState.Added;

On that last point - it might even be that just db.CartItems.Attach(cartItem); is enough as EF sets state to Added for entities that don't have their primary key fields set. My memory is a bit fuzzy on that one. In any case this is a good place to put a breakpoint and debug to see how EF does it's tracking magic. There is a great blog post on this subject, which I recommend:
https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/april/data-points-why-does-entity-framework-reinsert-existing-objects-into-my-database

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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