3

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.

RSinohara
  • 575
  • 3
  • 23
  • I have changed the test before attach to POCO.Lesson localLesson = context.lessons.Find(myObject.ID); which returns true, so it doesn't attach. No more exception, but I'm back to it creating a new unit, lesson and program instead of using the existing ones. I'm close to trying the desperate method of enclosing the attach in a try catch, and that will probably not work (it will not attach, hence create new entities). – RSinohara Mar 18 '14 at 04:22
  • This is beyond frustrating there's no way this should be this hard. – RSinohara Mar 18 '14 at 04:24
  • It is a lot easier if you use a generic Repository pattern. – failedprogramming Mar 18 '14 at 04:34
  • The problem here - maybe a problem, is that some words I add are new, some exist, and some are changed (their parent change). All units exist, as do lessons and programs. So I need to attach existing entities to avoid creating others, and (I think that's an issue) the object graph contains paths to bot new and existing entities. – RSinohara Mar 18 '14 at 04:58
  • generic Repository pattern seems nice, but I will not move to a more complex patter if I can't figure out this simple use of entity framework. – RSinohara Mar 18 '14 at 05:15

4 Answers4

3

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

Community
  • 1
  • 1
RSinohara
  • 575
  • 3
  • 23
1

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.

failedprogramming
  • 2,432
  • 15
  • 19
  • I can't attach, it throws the object with same key exists. – RSinohara Mar 18 '14 at 04:18
  • This could be true down the line, but it fails on the first unit, and I am adding words. All units already exist. Could this be because the units I am trying to attach has a relationship with other words, and attaching the unit would try to attach the words? – RSinohara Mar 18 '14 at 04:40
  • AttachIfNeeded is the first code in there, I'll add the stub for it. – RSinohara Mar 18 '14 at 04:42
  • The AttachIfNeeded method you provided takes a POCO.Program object but the error happens on an AttachIfNeeded method that takes a POCO.Unit object. Am I missing something here? – failedprogramming Mar 18 '14 at 05:32
  • No, I have an attachIFNeeded for all entity types, the error occurs in the very first unit it's called on. – RSinohara Mar 18 '14 at 14:58
1

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...

Bence Végert
  • 589
  • 5
  • 12
0

You can try this

context.words.Add(words);
result=context.SaveChanges();
  • I'm doing this afterwards, but if I don't attach before, it will create a new unit (and lesson and program), instead of using the existing ones. If I attach, it returns the error "Oject with the same key exists in the context". – RSinohara Mar 18 '14 at 04:19
  • Plus, words is a list, so it would have to be addRange (which I'm doing). – RSinohara Mar 18 '14 at 04:24
  • One of them suggest using the same context all the time (well, same context to pull the entities and save them, which would be all the time for me). This is what I was doing before, but I had a whole different type of problem (it said the entity had been disposed). – RSinohara Mar 18 '14 at 04:46
  • This is really what is frustrating, moving back and forth from implementations I don't really understand the used for an difference between them, running from a problem to find another. – RSinohara Mar 18 '14 at 04:48
  • This exception means that there is a duplicate entity with the same key already tracked by the context. Each entity can be tracked by the context only once. If you try to attach another instance of the same entity (it has the same key as already tracked instance) you will get this exception.You can try to use dbContext.ChangeTracker.Entries().FirstOrDefault(e => e.Id == entity.Id) to check if entity instance with the same key is already tracked – NullReferenceException Mar 18 '14 at 04:53
  • I'll try this, but already I know this: the first unit is not tracked (since the context is new), and it fails. Also, if I don't attach it, saveChanges will create a new unit, which is not intended. – RSinohara Mar 18 '14 at 05:03
  • So for either I attach the first unit and get an exception, or I don't and it adds a new one. I don't think another (and better) way to test will change this. – RSinohara Mar 18 '14 at 05:04