EntityFramework Code First - 检查实体是否已连接

50

我正在尝试在EntityFramework 4.3 Code First中更新一个带有FK关系的实体。 我试图通过调用:Entry(item).State = EntityState.Unchanged来附加到相关的实体

我得到了以下异常:在ObjectStateManager中已经存在具有相同关键字的对象。 ObjectStateManager无法跟踪具有相同关键字的多个对象。

我不会更新这些项目,也不会在我的主实体上具有id属性。 是否可能知道哪些实体已连接或未连接?

提前致谢, Radu


您可以使用 ObjectContext.ObjectStateManager.GetObjectStateEntries() 方法(http://msdn.microsoft.com/en-us/library/bb738497.aspx)获取上下文跟踪的所有实体。请注意,数据库中可能存在未被 ObjectContext 跟踪的实体。您需要提供一个状态以让 ObjectStateManager 知道您需要哪些实体(此枚举具有 Flags 属性,因此您可以使用 | 组合不同的值)。 - Pawel
这个回答解决了你的问题吗?什么是最合理的方法来确定实体是否已连接到dbContext?(这里接受的答案直接链接到可能的重复问题,而在这种情况下,它还比这个问题旧。) - ruffin
4个回答

93
您可以在这里找到答案。
public bool Exists<T>(T entity) where T : class
{
    return this.Set<T>().Local.Any(e => e == entity);
}

将这段代码放入您的上下文中,或者您可以将其转换为扩展程序。

public static bool Exists<TContext, TEntity>(this TContext context, TEntity entity)
    where TContext : DbContext
    where TEntity : class
{
    return context.Set<TEntity>().Local.Any(e => e == entity);
}

3
谢谢,这也对我很有帮助。针对上述函数,还需要注意一点:你需要在声明中加入 where T : class,否则编译器会报错。 - Sylpheed
2
TContext似乎不是必需的。让第一个参数的类型为DbContext - public static bool Exists<TEntity>(this DbContext context, TEntity entity)... - Peter Hedberg
1
@Palpie 我提供的答案是针对 EF 4.5 的,所以当时 DbContext 还不存在。不过这是个好建议。 - Tri Q Tran
1
“where TContext : DbContext, TEntity : class” 在我的系统上无法工作,必须替换为“where TContext : DbContext where TEntity : class”。也就是说,用“where”代替了逗号。 - Atron Seige
1
请记住,Local 还可以包含由上下文跟踪但尚未在数据库中的新实体。如果您同时更新和插入实体,则使用此方法确定实体是否已附加以进行更新可能会产生意外结果。 - Rudey
显示剩余5条评论

11
你可以使用这种方法:
    /// <summary>
    /// Determines whether the specified entity key is attached.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="key">The key.</param>
    /// <returns>
    ///   <c>true</c> if the specified context is attached; otherwise, <c>false</c>.
    /// </returns>
    internal static bool IsAttached(this ObjectContext context, 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;
    }

例如:

     if (!_objectContext.IsAttached(entity.EntityKey))
        {
            _objectContext.Attach(entity);
        }

17
我进行了一些性能测试,(出乎意料地)发现ObjectStateManager.TryGetObjectStateEntry的速度比Set<TEntity>().Local.Any()慢了70多倍。 - Brent

1
如果您从一个EF Core懒加载场景中到达这里,在该场景中,导航属性是通过DbSet<>.Include()子句在数据层中填充的,而实体附加到DbContext并且该实体被分离并传递到业务层,请考虑在DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder)方法中添加以下内容: optionsBuilder.ConfigureWarnings(warn => warn.Ignore(CoreEventId.LazyLoadOnDisposedContextWarning)); 该错误将被忽略,并返回最初包含的值。

0

我使用这个扩展方法,因为我需要基于值而不是实例来检查跟踪。

internal static class DBExtensions
    {
        internal static bool IsAttached<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> condition) where TEntity : class
        {
            return dbSet.Local.Any(condition);
        }
    }

使用方法:

 if (!context.Items.IsAttached(y => y.ItemId == item.ItemId))
    {
        context.Items.Attach(item);
    }

不,被接受的答案完全符合预期。重要的是要知道特定实例已经附加,否则您可能会开始修改另一个未附加的实例(它会给出IsAttached = true),并想知道为什么更改没有保存。 - Gert Arnold
@GertArnold 感觉这也是一个要点。修改了答案,因为它只是特定用例的解决方案。 - Beingnin

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