71

when i try to attach entity to context i get an exception

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

This is expected behaviour.

But i would like to know how ObjectStateManager knows that? I would like to do this check by myself before

maxlego
  • 4,864
  • 3
  • 31
  • 38
  • 1
    I suspect it interrogates the current context for an entity with the same key, you could surely do the same `var exists = (dbContext.entities.Where(e=>e.ID == myEntity.ID).Count() > 0);` or some such – Lazarus May 17 '11 at 15:46
  • +1 for doing the validation, but i would rather use Any insted of Count – Bongo Sharp May 17 '11 at 21:22
  • How can this be the expected behavior? So you have to check whether an object is already in the context every time? wtf? – Ian Warburton Feb 11 '14 at 15:28
  • 1
    @IanWarburton Yes. Entity framework does leave a lot of loose ends. In essence it useful only for simple data access. – f470071 Apr 27 '17 at 14:11

6 Answers6

103

If you are using DbContext API (you mentioned ef-code-first) you can simply use:

context.YourEntities.Local.Any(e => e.Id == id);

or more complex

context.ChangeTracker.Entries<YourEntity>().Any(e => e.Entity.Id == id);

In case of ObjectContext API you can use:

context.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached)
                          .Where(e => !e.IsRelationship)
                          .Select(e => e.Entity)
                          .OfType<YourEntity>()
                          .Any(x => x.Id == id);
podiluska
  • 50,950
  • 7
  • 98
  • 104
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • Thanks! Works like a charm. But maybe you have any suggestion how to do this if i dont know the type. i get set based on the type - context.Set(typeX). – maxlego May 17 '11 at 22:03
  • I expect this should still work: `context.Set(type).Local.Any(...)` – Ladislav Mrnka May 17 '11 at 22:05
  • Or perhaps not because if you don't have strongly typed `Set` you cannot use linq to query by properties of the type. – Ladislav Mrnka May 17 '11 at 22:07
  • 2
    But you can override equals in your entities and simply call `Any(e => e.Equals(yourEntity))` – Ladislav Mrnka May 17 '11 at 22:09
  • almost. objects were not equal, so i had to search key properties with reflection and compare properties values – maxlego May 17 '11 at 23:37
  • Hi @LadislavMrnka in your example, isn't ChangeTracker() a property: ChangeTracker rather than method? Unless it was a method in an older version of EF. – Alex KeySmith Sep 10 '12 at 11:16
  • @AlexKey: Yes, my fault. It is a property. I will fix the answer. – Ladislav Mrnka Sep 10 '12 at 11:42
  • 1
    @achekh - I tried this in EF5 but it doesn't work, if you read the comment on the builin Equals method it says "Two System.Data.Entity.Infrastructure.DbEntityEntry instances are considered equal if they are both entries for the same entity on the same System.Data.Entity.DbContext." - meaning that they need to have reference to the same entity object. – Ventsyslav Raikov Apr 09 '13 at 07:16
  • 1
    @Bond - you're right, unfortunately the DbEntityEntry.Compare doesn't regard for the keys, only entry and entity reference. Then the fastest way to make compare would be to enforce comparing EntityKey instances which you can get using `((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity).EntityKey` – achekh Apr 09 '13 at 10:35
13

Here's an extension method for getting the object from the context without having to worry about whether it is already attached:

public static T GetLocalOrAttach<T>(this DbSet<T> collection, Func<T, bool> searchLocalQuery, Func<T> getAttachItem) where T : class
{
    T localEntity = collection.Local.FirstOrDefault(searchLocalQuery);

    if (localEntity == null)
    {
        localEntity = getAttachItem();
        collection.Attach(localEntity);
    }

    return localEntity;
}

Just call:

UserProfile user = dbContext.UserProfiles.GetLocalOrAttach<UserProfile>(u => u.UserId == userId, () => new UserProfile { UserId = userId });
David Sherret
  • 101,669
  • 28
  • 188
  • 178
5

check

entity.EntityState == System.Data.EntityState.Detached

before attaching

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
Kaido
  • 3,383
  • 24
  • 34
  • 12
    The entity I'm adding is detached, but there might be entity loaded into dbcontext with same id already. – maxlego May 17 '11 at 20:21
2

Note that if change tracking is disabled on your context, asking the ObjectStateManager or the ChangeTracker might return that the object is not in the ObjectContext even if it is in fact already in there. Therefore, if you try to attach such object it will raise an exception.

context.Set<T>().Local.Any(e => e.Id == id);

works event if change tracking is disabled.

if you do not know the type of the object, there is various approach, either you define a method using reflection or other techniques like this one int GetIdOf(object entity){...}

Or you define an interface used by your classes like

public interface IMyEntity
{
    int Id{get;set;}
}

and use it this way :

context.Set(e.GetType()).Local.Cast<IMyEntity>().Any(e => e.Id == id);
Dubbs777
  • 347
  • 2
  • 7
0

you can query the dbContext with the "Any" extension method:

bool alreadyInDB = dbContext.Entity.Where(a=>a.ID==myEntity.id).Any();
Bongo Sharp
  • 9,450
  • 8
  • 25
  • 35
0

If you have arrived here, as I did, from an EF Core Lazy Loading scenario in which Navigation properties were filled in a data layer via DbSet.Include() clause(s) while the Entity was attached to a DbContext and then that Entity was detached and passed up to a business layer, consider adding something like this to your DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) method:

optionsBuilder.ConfigureWarnings(warn => warn.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning));

The error will be ignored and the values that were originally Include()d will be returned.

KramFfud
  • 179
  • 1
  • 2
  • 6