96

I am getting the following error when trying to attach an object that is already attached to a given context via context.AttachTo(...):

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

Is there a way of achieving something along the lines of:

context.IsAttachedTo(...)

Cheers!

Edit:

The extension method Jason outlined is close, but it doesn't work for my situation.

I am trying to do some work using the method outlined in the answer to another question:

How do I delete one or more rows from my table using Linq to Entities *without* retrieving the rows first?

My code looks a bit like this:

var user = new User() { Id = 1 };
context.AttachTo("Users", user);
comment.User = user;
context.SaveChanges();

This works fine, except when I do something else for that user where I use the same method and try to attach a dummy User object. This fails because I have previously attached that dummy user object. How can I check for this?

Community
  • 1
  • 1
joshcomley
  • 28,099
  • 24
  • 107
  • 147

5 Answers5

62

Here's what I ended up with, which works very nicely:

public static void AttachToOrGet<T>(this ObjectContext context, string entitySetName, ref T entity)
    where T : IEntityWithKey
{
    ObjectStateEntry entry;
    // Track whether we need to perform an attach
    bool attach = false;
    if (
        context.ObjectStateManager.TryGetObjectStateEntry
            (
                context.CreateEntityKey(entitySetName, entity),
                out entry
            )
        )
    {
        // Re-attach if necessary
        attach = entry.State == EntityState.Detached;
        // Get the discovered entity to the ref
        entity = (T)entry.Entity;
    }
    else
    {
        // Attach for the first time
        attach = true;
    }
    if (attach)
        context.AttachTo(entitySetName, entity);
}

You can call it as follows:

User user = new User() { Id = 1 };
II.AttachToOrGet<Users>("Users", ref user);

This works very nicely because it's just like context.AttachTo(...) except you can use the ID trick I cited above each time. You end up with either the object previously attached or your own object being attached. Calling CreateEntityKey on the context makes sure it's nice and generic and will work even with composite keys with no further coding (because EF can already do that for us!).

Edit, twelve years later (Dec 2021)... oof!

Here's what I use in EF Core:

public static class EfExtensions
{
    public static T AttachToOrGet<T>(this DbContext context, Func<T,bool> predicate, Func<T> factory)
        where T : class, new()
    {
        var match = context.Set<T>().Local.FirstOrDefault(predicate);
        if (match == null)
        {
            match = factory();
            context.Attach(match);
        }

        return match;
    }
}

Usage:

var item = db.AttachToOrGet(_ => _.Id == someId, () => new MyItem { Id = someId });

You could refactor this to work with the entity key but this is enough to get anyone going!

joshcomley
  • 28,099
  • 24
  • 107
  • 147
  • I was having a similar problem, and this just solved mine - brilliant, cheers! +1 – RPM1984 Nov 21 '10 at 23:46
  • 4
    Would be even better when the string parameter is replaced by a selector function for the collection the entity belongs to. – Jasper Apr 10 '11 at 16:34
  • 1
    Any idea why `(T)entry.Entity` sometimes returns null? – Tr1stan Oct 10 '11 at 11:39
  • 1
    I can't figure out what I should be setting my entitySetName to. I keep getting an exception. I am really frustrated because all i want to do is delete a user I don't want to have to deal with so much hidden non-sense blowing my application up. – Julie Feb 06 '13 at 17:03
  • Exactly my problem, except that I use the Database first approach. Does anybody know how to do this for Database first? – R. Schreurs Apr 05 '13 at 13:11
  • @Schreurs: I am using database first as well and it works fine. Have you specified it as "EntityContainer.EntitySet" for the entitySetName? (Insert your names into the string) – Matt Sep 06 '13 at 16:54
  • 1
    if `T` is already `IEntityWithKey`, can't you just use its `entity.EntityKey` property rather than reconstruct it or need to guess/provide the `EntitySetName`? – drzaus Oct 18 '13 at 16:30
  • Any way of doing this generically where you won't know the type or set name ahead of time? – Jason Coyne Jun 19 '15 at 16:47
62

A simpler approach is:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
  • 5,944
  • 4
  • 39
  • 44
  • 1
    This worked for me, just I had to use "EntityState.Detached" instead of just "detached"... – Marcelo Myara Oct 31 '13 at 11:56
  • 25
    Hmm tried Your solution, but for me isDetached is true, but still the same error when I try to Attach entry to context – Prokurors Mar 08 '14 at 15:45
  • Excellent appraoch. Even it worked with my Generic Repository. – ATHER Jan 05 '15 at 01:16
  • 6
    @Prokurors I recently learned the reason Mosh's check is not always enough to prevent the error, is that `.Include()`'ed navigation properties are also attempted to be attached when you call `.Attach` or sets its `EntityState` to `EntityState.Unchanged` - and they will conflict if any of the entities refer to the same entity. I haven't figure out how to only attach the base entity, so I had to redesign the project a bit, to use separate contexts for each "business transaction", like it was designed for. I'll note this for future projects. – Aske B. Jan 30 '18 at 14:40
18

Try this extension method (this is untested and off-the-cuff):

public static bool IsAttachedTo(this ObjectContext context, object entity) {
    if(entity == null) {
        throw new ArgumentNullException("entity");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

Given the situation that you describe in your edit, you might need to use the following overload that accepts an EntityKey instead of an object:

public static bool IsAttachedTo(this ObjectContext, EntityKey key) {
    if(key == null) {
        throw new ArgumentNullException("key");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(key, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

To build an EntityKey in your situation, use the following as a guideline:

EntityKey key = new EntityKey("MyEntities.User", "Id", 1);

You can get the EntityKey from an existing instance of User by using the property User.EntityKey (from interface IEntityWithKey).

jason
  • 236,483
  • 35
  • 423
  • 525
  • This is very good, but it doesn't work for me in my situation... I'll update the question with details. p.s. you want bool not boolean, and static, but other than that pretty awesome extension method! – joshcomley Nov 11 '09 at 14:51
  • @joshcomley: I think that you can address using an overload of `TryGetObjectStateEntry` that accepts an `EntityKey` instead of an `object`. I have edited accordingly. Let me know if this doesn't help and we'll go back to the drawing board. – jason Nov 11 '09 at 15:36
  • Ah just saw this - I was working on a solution outlined in an answer I just posted. +1 for your help and pointers!! – joshcomley Nov 11 '09 at 15:54
  • Any ideas why i am getting an error, the details is here http://stackoverflow.com/questions/6653050/ef-4-0-isattachedto-extension-method-and-error-an-object-with-the-same-key-alread – Joper Jul 11 '11 at 16:04
7

Using the entity key of the object you are trying to check:

var entry = context.ObjectStateManager.GetObjectStateEntry("EntityKey");
if (entry.State == EntityState.Detached)
{
  // Do Something
}

Kindness,

Dan

Daniel Elliott
  • 22,647
  • 10
  • 64
  • 82
0

This does not directly answer OPs question but this is how I solved mine.

This is for those who are using DbContext instead of ObjectContext.

    public TEntity Retrieve(object primaryKey)
    {
        return DbSet.Find(primaryKey);
    }

DbSet.Find Method:

Finds an entity with the given primary key values. If an entity with the given primary key values exists in the context, then it is returned immediately without making a request to the store. Otherwise, a request is made to the store for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found in the context or the store, then null is returned.

Basically, it returns the attached object of the given primaryKey so you just need to apply the changes on the returned object to keep the right instance.

Lawrence
  • 334
  • 2
  • 12