RSinohara
RSinohara

Reputation: 732

Entity framework attach: object with same key exists

I am building a windows form application, and I use multiple DBContext instances (mostly one per Business layer call).

After literally days of dealing with an issue (while inserting new entities, the ones they referred to were added as new ones, instead of using the existing entities), I found out that the problem was I had to attach the existing entities to the context.

All was good for about 2 hours, when I then got errors while attaching: the entity with the same key exists in the context.

I tried testing before attaching (similar method for every entity type):

    private void attachIfNeeded(POCO.Program myObject, myContext context)
    {
    if (!context.Set<POCO.Program>().Local.Any(e => e.ID == myObject.ID))
        {
            context.programs.Attach(myObject);
            return true;
        }
        else
        {
            myObject = context.Set<POCO.Program>().Local.Single(e => e.ID == myObject.ID);
            return false;
        }
}

But the tests return false, but it still fails when attaching.

So basically, if I don't attach, it will add a new entity instead of using the existing (and intended) one. If I do attach, there's an error I can't figure out.

I have looked around (doing this the whole day now) and I actually (think I) know what the problem is:

The entity I am trying to add has multiple relationships, and other entities can be reached by multiple paths. Could that cause the problem?

Please help with this, solutions out there really make no sense to me and haven't worked.

I am really close to the point where I will try-catch around the attach statement and be done with it. But I will hate doing it.

Here are my entities (not all of them, but this should be enough):

 public class Word
{
    [Key]
    public int ID {get;set;}

    [Required]
    public string word { get; set; }


    public WordCategories category { get; set; }

    public Word parent {get;set;}

    public List<Unit> units { get; set; }

    public Program program { get; set; }

    public List<Lesson> lessons { get; set; }

    public Word()
    {
        units = new List<Unit>();
        lessons = new List<Lesson>();
    }
}
public class Unit
{
    [Key ]
    public int ID { get; set; }

    [Required]
    public string name { get; set; }

    public string description { get; set; }

    public List<Lesson> lessons { get; set; }

    public Program program {get;set;}

    public List<Word> words { get; set; }

    public Unit()
    {
        lessons=new List<Lesson>();
        words = new List<Word>();
    }

}

And here is where I am calling the attach method. The error is thrown on the first attach:

public int addWords(List<POCO.Word > words,int programID, int unitID,int lessonID)
     {
         CourseHelperDBContext context = getcontext();

         int result;



        foreach(POCO.Word a in words)
        {
            foreach (POCO.Unit b in a.units)
                attachIfNeeded(b, context);
            foreach(POCO.Lesson c in a.lessons )
                attachIfNeeded(c,context);
            attachIfNeeded(a.program,context);
            if (a.parent != null)
                attachIfNeeded(a.parent,context);
        }

         context.words.AddRange(words);
         result = context.SaveChanges();
         return result;

     }

I cannot believe I'm having so many issues with this. I just want to store those entities, add some (I haven't gotten to the point where I would change them) and save it.

So far I've figured:

  1. Some words are new, some exist and some are changed (mostly parent property);
  2. All units exist, as do programs and lessons (so I need to attach them);
  3. The object graph contains multiple paths to entities, some of which exist, some of which are new;
  4. I am using a new context for every request. I run into other issues when I was using the same all the time. I found solutions that pointed to this pattern, and I think it's OK since that's what you'd do on an ASP MVC project.

All these could be causing problems, but I don't know which and how to work around them.

I think I can make this work by adding one word at a time, and pulling programs, lessons and units every time... but that means many many round trips to the DB. This can't be the way.

Upvotes: 4

Views: 10665

Answers (4)

Bence V&#233;gert
Bence V&#233;gert

Reputation: 788

I share my experience with the same exception. First, here is my code:

        public void UpdateBulk(IEnumerable<Position> pDokumentPosition, DbDal pCtx)
        {
                foreach (Position vPos in pDokumentPosition)
                {
                    vPos.LastDateChanged = DateTime.Now;
                    pCtx.Entry(vPos).State = EntityState.Modified;
                 }
                pCtx.SaveChanges();
        }

I got the same exception on the EntityState.Modified line.

In my case the problem was that, when set the vPos state to modified, then all the related objects (vPos.Document and vPos.Produkt) loaded in the context with unchanged state. In the foreach first step it not makes any exception, just on the second step, because eg. the related Dokument entity has already been loaded in the memory/context (so the key property of the Dokument too).

And how i solve it? (maybe not the best solution):

I detach the related entites in every step with this lines:

        if (vPos.Dokument != null)
        {
            pCtx.Entry(vPos.Dokument).State = EntityState.Detached;
        }

        if (vPos.Produkt!=null)
        {
            pCtx.Entry(vPos.Produkt).State = EntityState.Detached;
        }

If you have better solution, I'm looking forward to it...

Upvotes: 1

RSinohara
RSinohara

Reputation: 732

Back to this after quite some time, the problem in this case was that I needed to retrieve the entities that were present on my relationships.

The solution was neither attach (because it would fail if the entity is already attached) nor add (because it already existed on the DB).

What I should have done was to retrieve every entity related to the one I was adding.

This helped: Entity Framework creating new entity with relationship to existing entity, results in attempt to create new copy of the existing entity

Upvotes: 3

failedprogramming
failedprogramming

Reputation: 2522

After attaching the entity, try setting the entity state to modified.

context.programs.Attach(myObject);
context.Entry(myObject).State = EntityState.Modified;

I think there's a mistake in your test logic.

If entity does not exist in database, you should be adding instead of attaching. Your code is attaching if it can't find an entity when it should really be adding.

Code to add a new entity (Create/Insert)

context.Set<T>.Add(entity);

Code to attach an entity (Update)

context.Set<T>.Attach(entity);
context.Entry(entity).State = EntityState.Modified;

If your code is failing on the first attach, that would be attachIfNeeded(b,context); ? I don't think you have shown us the code for this.

Upvotes: 1

NullReferenceException
NullReferenceException

Reputation: 1639

You can try this

context.words.Add(words);
result=context.SaveChanges();

Upvotes: 0

Related Questions