最合理的方法是如何确定实体是否已连接到dbContext?

72

当我尝试将实体附加到上下文时,我收到了一个异常。

具有相同键的对象已经存在于ObjectStateManager中。 ObjectStateManager无法跟踪具有相同键的多个对象。

这是预期行为。

但是我想知道ObjectStateManager是如何知道的?我想在此之前自己进行此检查。


1
我怀疑它会查询当前上下文中是否存在具有相同键的实体,你肯定可以执行相同的操作 var exists = (dbContext.entities.Where(e=>e.ID == myEntity.ID).Count() > 0); 或者类似的操作。 - Lazarus
+1 意味着你完成了验证,但我宁愿使用 Any 而不是 Count。 - Bongo Sharp
这怎么可能是期望的行为呢?所以每次都要检查对象是否已经在上下文中?什么鬼? - Ian Warburton
1
@IanWarburton 是的。实体框架确实留下了很多松散的尾巴。本质上,它只适用于简单的数据访问。 - f470071
6个回答

105

如果你正在使用 DbContext API(你提到了 ef-code-first),你可以简单地使用以下代码:

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

或更复杂

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

如果使用ObjectContext API,您可以使用以下方法:

context.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached)
                          .Where(e => !e.IsRelationship)
                          .Select(e => e.Entity)
                          .OfType<YourEntity>()
                          .Any(x => x.Id == id);

我认为这仍然有效:context.Set(type).Local.Any(...) - Ladislav Mrnka
或许不是因为如果您没有强类型的“Set”,则无法使用LINQ按类型属性查询。 - Ladislav Mrnka
2
但是您可以在实体中重写equals方法,然后简单地调用Any(e => e.Equals(yourEntity)) - Ladislav Mrnka
1
@achekh - 我在EF5中尝试了这个,但它不起作用。如果你读一下Equals方法的注释,它说:“如果两个System.Data.Entity.Infrastructure.DbEntityEntry<TEntity>实例都是指向同一个System.Data.Entity.DbContext上的同一个实体,则认为它们相等。”- 这意味着它们需要引用相同的实体对象。 - Ventsyslav Raikov
1
@Bond - 你说得对,不幸的是,DbEntityEntry.Compare 不考虑键,只考虑实体和实体引用。因此,最快的比较方法是强制比较 EntityKey 实例,可以使用 ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity).EntityKey 获取。 - achekh
显示剩余4条评论

13
这里是一个扩展方法,可以在不必担心对象是否已经附加的情况下从上下文中获取对象:
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;
}

只需要调用:

UserProfile user = dbContext.UserProfiles.GetLocalOrAttach<UserProfile>(u => u.UserId == userId, () => new UserProfile { UserId = userId });

6

检查

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

在附加之前


13
我要添加的实体是分离的,但是可能已经有相同ID的实体被加载到DbContext中了。 - maxlego

2
请注意,如果您的上下文中禁用了更改跟踪功能,则询问ObjectStateManager或ChangeTracker可能会返回该对象不在ObjectContext中的信息,即使它实际上已经在其中。因此,如果您尝试附加此类对象,将会引发异常。
context.Set<T>().Local.Any(e => e.Id == id);

即使关闭更改跟踪,也可以工作。

如果您不知道对象的类型,则有不同的方法,可以使用反射或其他类似于此代码的技术定义一个方法:

int GetIdOf(object entity){...}

或者您可以定义一个由您的类使用的接口:

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

并且这样使用它:

context.Set(e.GetType()).Local.Cast<IMyEntity>().Any(e => e.Id == id);

".Set是一个方法,因此第一个片段需要在<T>后面加上括号。" - reggaeguitar

0

您可以使用“Any”扩展方法查询dbContext:

bool alreadyInDB = dbContext.Entity.Where(a=>a.ID==myEntity.id).Any();

0
如果您像我一样,从一个EF Core懒加载场景中到达了这里,在该场景中,导航属性通过DbSet.Include()子句在数据层中填充,而实体附加到DbContext中,然后该实体被分离并传递到业务层,请考虑在您的DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder)方法中添加类似以下内容的代码:
optionsBuilder.ConfigureWarnings(warn => warn.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning));

错误将被忽略,并将返回最初包含(Include())的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接