EF relationships and the difficulties to add entity

I had an interesting question.

This code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test”, Categories = c };

    Console.WriteLine(c.Products.Count);

}


 

attach p to the context. Why?

You probably know that Entity Framework doesn’t support Lazy Loading. So when you do p.Categories, you will have the category in the ObjectContext ObjectStateManager. If it isn’t loaded, you will have null.

So, when you affect a category (existing in the context) to a new product, the product will also be attached.

If you don’t want this, you must use EntityReference:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test” };

    p.CategoriesReference.EntityKey = c.EntityKey;

}

Attach to context implies that following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test”, Categories = c };

    Console.WriteLine(c.Products.Count);

    p.Categories = context.Categories.First(categ => categ.CategoryID == 2);

    Console.WriteLine(c.Products.Count);

}


 

 

will return 1 then 0.

 

With following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test”, Categories = c };

    context.SaveChanges();

}

 

it may seem strange that you will have a new DB record without calling Add on the context (neither context.AddToProduct, nor context.AddObject). In fact p.Categories = c attaches p to the context AND changes p EntityState from Detached to Added. It’s why SaveChanges adds a record in DB.

If you remove c ChangeTracker before linking it to p:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = “test”};

    p.Categories = c;

    context.SaveChanges();

}

 

When you affect null to c ChangeTracker, this means that you will be able to attach c to another context but until you do it c EntityChangeTracker keeps the old one:
 

private IEntityChangeTracker EntityChangeTracker

{

    get

    {

        if (this._entityChangeTracker == null)

        {

            this._entityChangeTracker = s_detachedEntityChangeTracker;

        }

        return this._entityChangeTracker;

    }

    set

    {

        this._entityChangeTracker = value;

    }

}

 

So in this case, you will have a new DB record.

Now, what does happen in this case?

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = “test”};

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

        p.Categories = c;

    }

    var c2 = context.Categories.First(categ => categ.CategoryID == 1);

    Console.WriteLine(c2.Products.Count);

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

p isn’t saved on DB because it is linked to context2 but, Console shows 1 and 1 because with cache use, second context.Categories.First(categ => categ.CategoryID == 1) returns c.

So following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = “test”};

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

    }

    var c2 = context.Categories.First(categ => categ.CategoryID == 1);

    p.Categories = c2;

    Console.WriteLine(c2.Products.Count);

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

doesn’t add product DB record.
 
It’s strange. So to add my product DB record in this scenario, I need to do this:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = “test” };

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

        var c2 = context.Categories.First(categ => categ.CategoryID == 1); // useless, we should use directly c in this case


        p.Categories = c2;

        context2.SaveChanges();

    }

}


Now, what does happen with Detach?

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    var p = new Products { ProductName = “test”};

    p.Categories = c;

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

Of course, p won’t be saved in DB but console shows 1. Indeed, when you detach an entity, this one is attached to a DetachedEntityChangeTracker which will include p.

Last point. This code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    var p = new Products { ProductName = “test”};

    p.Categories = c;

    context.AddToProducts(p);

}

 

fails with an InvalidOperationExceptionThe object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.” on the Add.

But not this one:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test”};

    p.Categories = c;

    context.AddToProducts(p);

}

 

Why?

There are two points. When you detach an entity, context ObjectStateManager will keep cache information. Then, when you do Add on the context, you will add all the graph. So, you add a new product AND our category which will generate a conflict with ObjectStateManager cache (=> exception). In the second case, nothing is done on the Add. Indeed, if the same entity is already in the context with an Added EntityState (it’s the case), Add will do nothing and the product graph Add won’t add the category because this one is already attached to the context.

Moreover, note that following code:

using (var context = new NorthwindEntities())

{

    var p = new Products { ProductName = “test”};

    context.AddToProducts(p);

    context.AddToProducts(p);

    context.SaveChanges();

}

 

will generate only one DB record.

Finally, this code:

using (var context = new NorthwindEntities())

{

    var c = new Categories { CategoryName = “test” };

    var p = new Products { ProductName = “test” };

    p.Categories = c;

    context.AddToProducts(p);

}

 

will add the product and the category to the context without problem (of course).

Following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        var p = new Products { ProductName = “test” };

        p.Categories = c;

        context2.AddToProducts(p);

    }

}

 

will fail for the same raison than before but not this one:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        context2.AttachTo(“Categories”, c);

        var p = new Products { ProductName = “test” };

        p.Categories = c;

        context2.AddToProducts(p);

    }

}

 

When you attach c, its EntityState pass from Detached to Unchanged. Note that in this case AddToProducts is useless because category is attached to the context when you do context2.AttachTo(“Categories”, c) and so you are on the same case than before:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = “test” };

    p.Categories = c;

}

 

But in this case:

 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        var p = new Products { ProductName = “test” };

        p.Categories = c;

        context2.AttachTo(“Categories”, c);

        context2.AddToProducts(p);

    }

}

 

code fails with an InvalidOperationExceptionAn object with the same key already exists in the ObjectStateManager. The existing object is in the Unchanged state. An object can only be added to the ObjectStateManager again if it is in the added state.” because when you attach the category, you attach all the graph, so you attach p with Unchanged EntitySet and when you want to add it, you get the exception.
This entry was posted in 7671, 7674. Bookmark the permalink.

8 Responses to EF relationships and the difficulties to add entity

  1. Farzad Badili says:

    Thanks for your detailed explanation. It was very useful for me and helped me so much.

    Best Regards,
    Farzad Badili

  2. Matthieu MEZIL says:

    You’re welcome :-)

  3. Tony D says:

    Thanks Matthieu, great write up, great information. Always appreciated!

  4. Michael says:

    You saved a lot of my hair. I’ve been pulling it out trying to figure some of this stuff out. You just turned on the light. Thanks.

  5. Roberto says:

    Hi, I have a problem, I can not update a foreign key in my table, please help me!!!

  6. Matthieu MEZIL says:

    Hi Roberto.

    I think you should look at this.

  7. Dave says:

    This is ridiculous. Plain and simple ridiculous.

  8. Nick Chen says:

    Good job! Thanks a lot, it’s very helpful

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>