18

I've stumbled upon a strange bug in my code. Which was working before, but now works sometimes.

I am using EF6 to Edit an entity with some relations. To not edit the relations I 'Attach' them (see example code).

public void EditA(A ThisIsA, B ThisIsB)
    {
        using (var Context = new LDZ_DEVEntities())
        {
            Context.As.Attach(ThisIsA);

            var b = Context.Bs.FirstOrDefault(x => x.BId == ThisIsB.BId);
            //var b = Context.Bs.Find(ThisIsB.BId);

            if (b != null)
                Context.Bs.Attach(b);
            else
                b = ThisIsB;

            if (b.C != null)
                Context.Cs.Attach(b.C);

            ThisIsA.Bs.Add(b);

            Context.SaveChanges();

        }
    }

I've edited the names to keep it simple.

The following line

Context.Cs.Attach(b.C);

throws this error:

Attaching an entity of type 'C' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

This line was introduced because all the C entities are static entities. I never want a C to be created. If I remove this line, every time when I will add a B to A; a C is created. Which is not desirable.

Extra info:
A has a list of B's
B has one C

This EditA() Method is being called at multiple places in my software. This error only appears when the method is called in a loop (import). There are no problems while working on the first record. But I’m getting the error in the records after the first one.

I've read this questions plus answers but they weren't working for me:

  1. ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value

  2. Attaching an entity of type failed because another entity of the same type already has the same primary key value

Community
  • 1
  • 1
Robin Gordijn
  • 683
  • 1
  • 4
  • 16
  • 1
    If A has a list of B and B has one C, you only need to attach A. Bs and Cs will be automatically inserted... – Fabio May 20 '15 at 12:49
  • C's are static entities. I never want to Insert them. B's only have to be inserted if a B does not already exists. – Robin Gordijn May 20 '15 at 12:53
  • What do you mean by 'static entities'? According to your code there is a dataset of C's entities. How can they be 'static'? – Kryptos May 20 '15 at 14:23
  • Static as in, there is a set of C entities. That are never to be created / updated / deleted – Robin Gordijn May 20 '15 at 14:23
  • Related post that might help: http://stackoverflow.com/questions/23201907/asp-net-mvc-attaching-an-entity-of-type-modelname-failed-because-another-ent – Vaibhav Aug 06 '16 at 15:45
  • Please have a look at my answer on [ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value](http://stackoverflow.com/questions/23201907/asp-net-mvc-attaching-an-entity-of-type-modelname-failed-because-another-ent/39557606#39557606). – Murat Yıldız Sep 18 '16 at 12:25

3 Answers3

19

I fixed it.

In Fabio Luz his answer, he said:

//if A has been loaded from context
//dont attach it
//if it has been created outside of the context
//Context.Entry(ThisIsA).State = EntityState.Modified;

This got me thinking, so I edited my code to this:

public void EditA(A ThisIsA, B ThisIsB)
{
    using (var Context = new LDZ_DEVEntities())
    {
        var a = Context.As.Find(ThisIsA.AId);

        //var b = Context.Bs.FirstOrDefault(x => x.BId == ThisIsB.BId);
        var b = Context.Bs.Find(ThisIsB.BId);

        if (b != null)
            Context.Bs.Attach(b);
        else
            b = ThisIsB;

        if (b.C != null)
            Context.Cs.Attach(b.C);

        a.Bs.Add(b);

        Context.SaveChanges();

    }
}

Summary of changes:

  • Changed FirstOrDefault to Find
  • Get A from Context

At first I removed the Attach of C, as a result this created a new entity. So I reversed this change.

Special thanks to Fabio Luz. I couldn't have done this without your help!

Robin Gordijn
  • 683
  • 1
  • 4
  • 16
  • 1
    You are welcome mate! Thank you for the compliments. Mark your own post as answer to help the others in the future. :D – Fabio May 20 '15 at 16:33
  • This really helped me - I was loading the entity I was editing in a separate variable and didn't think the change tracker would be tracking a different version and comparing the two.. as soon as I removed the other reference then the change to EntityState.Modified worked like a charm. Thanks. – keithl8041 Oct 02 '15 at 16:16
  • No problem keithl8041, help voting this post usefull so we could help people finding the answer which might help them – Robin Gordijn Oct 12 '15 at 13:17
7

Take a look at the following link https://msdn.microsoft.com/en-us/data/jj592676.aspx

If you have an entity that you know already exists in the database but to which changes may have been made then you can tell the context to attach the entity and set its state to Modified. For example:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" }; 

using (var context = new BloggingContext()) 
{ 
    context.Entry(existingBlog).State = EntityState.Modified; 

    // Do some more work...  

    context.SaveChanges(); 
}

NOTE: you don't have to do this with all objects (A, B and C), just with A.

EDIT 1

Based on your comment, try this:

//check if 
var _b = Context.Bs.Find(ThisIsB.BId);

if (_b != null)
  //b doesn't exist, then add to the context
  //make sure that the primary key of A is set.
  //_b.PrimaryKeyOfA = someValue;
  Context.Bs.Add(_b);
else
 //b already exists, then modify the properties
 //make sure that the primary key of A is set.

Context.SaveChanges();

EDIT 2

I didn't tested but it should work.

public void EditA(A ThisIsA, B ThisIsB)
{
    using (var Context = new LDZ_DEVEntities())
    {
        //if A has been loaded from context
        //dont attach it
        //if it has been created outside of the context
        //Context.Entry(ThisIsA).State = EntityState.Modified;

        var _b = Context.Bs.Find(ThisIsB.BId);

        if (_b == null)
        { 
            _b = ThisIsB;
        }

        ThisIsA.Bs.Add(_b);

        Context.SaveChanges();

    }
}
Fabio
  • 11,892
  • 1
  • 25
  • 41
  • unfortunately this didn't fixed the problem. The method is used to add a B to A.. So I only want to register the change in relation between A and B.. and if B does not exists.. B will be created (which happens in the A.Bs.Add(B)). – Robin Gordijn May 20 '15 at 13:24
  • Take a look at **Edit 1** – Fabio May 20 '15 at 13:36
  • If you look at my example code you can see that I am already doing something like your edit. The error is being thrown when i try to attach C. Or do you think that it is caused by something else? – Robin Gordijn May 20 '15 at 13:43
  • I believe we are missing something here. Could you explain me exactly what do you want to do? I say this because your edit method have 3 parameters, and it looks like strange to me... it shouldn't be necessary – Fabio May 20 '15 at 14:06
  • I understand the confusion. C wasn't there as a parameter, I added it there to see if it may solved the problem. I've edited the code in my question. – Robin Gordijn May 20 '15 at 14:09
  • Ok, the things are getting clear... Everytime you call EditaA, the first parameter (objectA) already exists in the database, right? – Fabio May 20 '15 at 14:17
  • The second parameter (B). Is this a single record that should be added to the A's collection of B? OR is this a collection of B that should be Added to A? OR Is this a collection that represents the final state of A? which means, "delete the records that exists in database but not in B. add records that exists in B but not in database. edit records that exists in both" – Fabio May 20 '15 at 14:31
  • Object A has a list of B's. The second parameter (B) is a single record that should be added to this list. – Robin Gordijn May 20 '15 at 14:36
  • Take a look at **Edit 2** – Fabio May 20 '15 at 14:58
2

Another way, depending on your situation, is to simply Detach the Entity State.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Modify(Model model)
{

if (model.Image == null)
{
Model item = db.Model.Find(model.Name);

// Get the Content needed:
model.Image = item.Image;

// Detach the Comparison State:
db.Entry(item).State = EntityState.Detached;
}

if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}

return View(model);
}

By doing this: db.Entry(item).State = EntityState.Detached; the State of the EntityFramework is still intact and you can save the changes into the Database (db).

Hope this helps!

Rusty Nail
  • 2,692
  • 3
  • 34
  • 55