【问题标题】:Is is possible to check if an object is already attached to a data context in Entity Framework?是否可以检查对象是否已附加到实体框架中的数据上下文?
【发布时间】:2009-11-11 14:27:37
【问题描述】:

尝试附加已通过context.AttachTo(...) 附加到给定上下文的对象时出现以下错误:

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

有没有办法实现以下目标:

context.IsAttachedTo(...)

干杯!

编辑:

Jason 概述的扩展方法很接近,但它不适用于我的情况。

我正在尝试使用另一个问题的答案中概述的方法做一些工作:

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

我的代码看起来有点像这样:

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

这很好用,除非我为该用户执行其他操作,我使用相同的方法并尝试附加一个虚拟的 User 对象。这失败了,因为我之前已经附加了那个虚拟用户对象。我该如何检查?

【问题讨论】:

    标签: .net entity-framework


    【解决方案1】:

    更简单的方法是:

     bool isDetached = context.Entry(user).State == EntityState.Detached;
     if (isDetached)
         context.Users.Attach(user);
    

    【讨论】:

    • 这对我有用,只是我不得不使用“EntityState.Detached”而不是“分离”...
    • 嗯尝试了你的解决方案,但对我来说 isDetached 是真的,但是当我尝试将条目附加到上下文时仍然出现同样的错误
    • 优秀的方法。甚至它也适用于我的通用存储库。
    • @Prokurors 我最近了解到 Mosh 的检查并不总是足以防止错误的原因是,当您调用 .Attach 或设置其EntityStateEntityState.Unchanged - 如果任何实体引用同一实体,它们将发生冲突。我还没有弄清楚如何只附加基础实体,所以我不得不重新设计项目,为每个“业务交易”使用单独的上下文,就像它设计的那样。我会在以后的项目中注意这一点。
    【解决方案2】:

    这是我最终得到的,效果很好:

    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);
    }
    

    你可以这样称呼它:

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

    这非常好用,因为它就像context.AttachTo(...) 一样,只是你每次都可以使用我上面引用的 ID 技巧。您最终会得到先前附加的对象或附加的您自己的对象。在上下文中调用 CreateEntityKey 可确保它很好且通用,并且即使使用复合键也无需进一步编码(因为 EF 已经可以为我们做到这一点!)。

    编辑,十二年后(2021 年 12 月)...哎呀!

    这是我在 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;
        }
    }
    

    用法:

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

    您可以重构它以使用实体键,但这足以让任何人开始!

    【讨论】:

    • 我遇到了类似的问题,这刚刚解决了我的问题 - 太棒了,干杯! +1
    • 将字符串参数替换为实体所属集合的选择器函数会更好。
    • 知道为什么(T)entry.Entity 有时会返回 null 吗?
    • 我不知道应该将我的 entitySetName 设置为什么。我不断收到异常。我真的很沮丧,因为我只想删除一个用户,我不想处理这么多隐藏的废话,让我的应用程序崩溃。
    • 如果T 已经是IEntityWithKey,你不能只使用它的entity.EntityKey 属性而不是重建它还是需要猜测/提供EntitySetName
    【解决方案3】:

    试试这个扩展方法(这是未经测试和现成的):

    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;
    }
    

    鉴于您在编辑中描述的情况,您可能需要使用以下接受 EntityKey 而不是对象的重载:

    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;
    }
    

    要根据您的情况构建EntityKey,请使用以下指南:

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

    您可以使用属性User.EntityKey(来自接口IEntityWithKey)从User 的现有实例中获取EntityKey

    【讨论】:

    • 这很好,但在我的情况下它对我不起作用......我会用细节更新问题。 p.s.你想要 bool 不是布尔值和静态的,但除了那个非常棒的扩展方法!
    • @joshcomley:我认为您可以使用TryGetObjectStateEntry 的重载来解决问题,它接受EntityKey 而不是object。我已经进行了相应的编辑。如果这没有帮助,请告诉我,我们将回到绘图板。
    • 啊刚刚看到这个 - 我正在研究我刚刚发布的答案中概述的解决方案。 +1 为您提供帮助和指点!!
    • 任何我收到错误的想法,详细信息在这里stackoverflow.com/questions/6653050/…
    【解决方案4】:

    使用您要检查的对象的实体键:

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

    善良,

    【讨论】:

      【解决方案5】:

      这并不能直接回答 OP 的问题,但这就是我解决问题的方法。

      这适用于使用DbContext 而不是ObjectContext 的用户。

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

      DbSet.Find 方法

      查找具有给定主键值的实体。如果一个实体与 给定的主键值存在于上下文中,那么它是 没有向商店提出请求,立即退货。否则, 向具有给定主节点的实体向商店发出请求 键值,并且该实体(如果找到)附加到上下文中,并且 回来。如果在上下文或商店中没有找到实体,则为 null 被退回。

      基本上,它返回给定primaryKey 的附加对象,因此您只需对返回的对象应用更改以保留正确的实例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-19
        • 2014-08-08
        • 2020-08-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多