2

Here's the flow of the program:

  1. Fetch a list of entities and use them. This will disconnect/detach all entities from the context.
  2. Make changes on one of the entities and save it. I'm loading the entity from the context and apply the changes (scalar properties and relational) of the detached entity to the freshly loaded entity.
  3. I have a feature where the user can revert all changes made on the disconnected entity. Here's the code I'm using:

        public async Task RevertChanges()
    {
        using (var db = new TwinTailDb())
        {
            //Fansubs.Clear();
    
            if (db.Entry(this).State == EntityState.Detached && Id != 0)
            {
                db.ArchiveEntries.Attach(this);
                await db.Entry(this).ReloadAsync();
            }
    
            //await db.Entry(this).Collection(a => a.Fansubs).LoadAsync();
        }
    }
    

However, when I attach the detached entity, it throws this exception:

Additional information: Attaching an entity of type 'TwinTail.Entities.ArchiveEntry' 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.

Note that the context is automatically disposed because I'm using the using statement.

I'm not sure why it even there is a conflict in the primary key since I didn't even load another entity since the previous context was already disposed.

Also, if I skip step 2 where I save all changes in the entity, it doesn't throw an exception. I'm left with thinking that somehow it's still being tracked .

EDIT: Here's what happens when I skip attaching, proving that the entity is really detached.

Additional information: Member 'ReloadAsync' cannot be called for the entity of type 'ArchiveEntry' because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet.

Seriously, what's happening :(

Sylpheed
  • 267
  • 1
  • 12
  • Do you dispose context in step 2? – Red Mar 12 '16 at 11:51
  • Well, it's automatically disposed since I'm using the "using" syntax. – Sylpheed Mar 12 '16 at 12:03
  • Wouldn't it just be easiest to query for the persisted entity by its PK and then use it from that point onwards? Or are you specifically trying to revert to the exact values that the entity had before, instead of the current persisted values? – Rytmis Mar 12 '16 at 13:03
  • I've thought of that but I don't want some other object keeping reference on the older entity. Architecture-wise, I want to keep the same reference (less moving parts). Yes, I'm trying to revert it back to the persisted values. – Sylpheed Mar 12 '16 at 13:47
  • I feel that this doesn't really have anything to do with "reverting" but more an attaching a detached entity, so I posted another question: http://stackoverflow.com/questions/35987384/reattaching-a-detached-entity-throws-an-exception Should I delete this or keep this open for others to see? – Sylpheed Mar 14 '16 at 12:22
  • You might 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:37

2 Answers2

0

Some actions on the entities that involve context might change the state to Attached. It could happen when passing an entity to a method in context. Try placing a breakpoint with condition to break when Entity state changes, and make sure that entity does not get attached before you actually call attach, as a side effect of other actions. If that is the case, than you're trying to attach an entity that is already attached, which should cause an exception.

ironstone13
  • 3,325
  • 18
  • 24
  • I also tried catching the exception and checked its state. It's saying that it's detached even after the exception. Also, the code posted above is the entire function that I call on the entity. I'm pretty sure it's always detached since I make sure that I dispose the context for every operation. – Sylpheed Mar 12 '16 at 16:56
  • can you please give more context on how is the object that contains RevertChanges method is instantiated, and what is it's lifetime, and whether it's long living and is potentially called from multiple threads? Is it possible that the RevertChanges Task is running in parallel to some other task that operates on **this** instance and works with the context. Also is _TwinTailDb_ directly generated by EF T4 template, or is it a class that wraps DbContext and adds some logic? – ironstone13 Mar 13 '16 at 07:36
  • Oh sorry. Yes, TwinTailDb is a generated DbContext (inherited) along with its models. I wrote partial classes for each model so that it's a lot easier to call. RevertChanges is one of those. Each function performs an operation on the context but is safely disposed right after the function ends. The entities are instantiated on the first Window since it displays all entities. The only tasks I did were the 3 steps I listed in the question. I'm sure that these 3 doesn't happen in the same time. In my application, user can only choose to Save or Close (which reverts the changes) the edit window. – Sylpheed Mar 13 '16 at 08:36
  • Cont'd. Most of the times, I await a Task using the UI context (by plainly awaiting inside async WPF event handlers). There are other times (like in Step1) where I perform operation using a different context (using Task.Run). I'm assuming that it won't matter since I'm not really running them in parallel. All my tasks, currently, run one by one. I suspected this to be a problem yesterday so I added some Console.WriteLine in Save and Revert. 100% of the time Save tasks finishes before I can call RevertChanges. – Sylpheed Mar 13 '16 at 08:47
0

Answering my own question.

The main problem was that the way I handled the lifetime of context is wrong. Also, attaching an entity that was loaded from another context will surely throw an error. Entities that are loaded in a context should only be used in that context.

My context is too short-lived. I'll adjust its lifetime so that there's one context for every transaction (database process).

Here's a very detailed article on how to solve/design your architecture: http://mehdi.me/ambient-dbcontext-in-ef6/

After fixing the architecture, reloading/reverting an entity can simply be done by calling:

DbContext.Entry(entity).Reload();
Sylpheed
  • 267
  • 1
  • 12