EF 4.0 IsAttachedTo扩展方法和错误具有相同键的对象已存在

时间:2011-07-11 16:02:57

标签: entity-framework entity-framework-4

我收到了错误

  

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

在我用谷歌搜索后,我在那里找到IsAttachedTo扩展方法:

Is is possible to check if an object is already attached to a data context in Entity Framework?

这是我的代码:

foreach (string s in types)
   {
    Subscription subscription = new Subscription { Id = Int32.Parse(s) };

    if (service.repository._context.IsAttachedTo(subscription))
        service.repository._context.Detach(subscription);

    service.repository._context.AttachTo("Subscriptions", subscription); //error here
    horse.Subscriptions.Add(subscription);
    }

但是当在foreach循环中出现具有相同密钥的订阅时,扩展方法IsAttachedTo每次都返回false,它不会检测到已经附加了这样的实体。结果我得到了同样的错误:

  

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

为什么会这样?

我该怎么做才能解决这个问题?

2 个答案:

答案 0 :(得分:3)

我对你的代码审查很少,因为你的示例代码让我感到害怕。

您可能已经阅读了许多关于花哨的设计模式和分层架构的内容,并且您开始自己使用它们。不幸的是你错过了重点。这到底是什么意思?

service.repository._context.XXX

如果他们没有封装他们的逻辑,为什么还要打扰任何服务层或存储库层?为什么要在服务上公开存储库?没有人应该了解服务内部实施?更糟糕的是,为什么要在存储库中公开上下文?这破坏了存储库的全部意义!

编写高质量的面向对象代码有很多支持规则。其中一条规则称为Law of Demeter。您不必遵循每条规则,也不必一直遵守规则,但在分层架构的情况下,这项法律是必须的。

如果你有三层A - >; B - > C层A可以调用B层上的方法,但它不知道C并且无法访问其方法。如果可以,它不是一个新层,但它与B是同一层,而A层不需要通过B调用它,它可以直接调用它。

在您的示例中,您刚刚将D暴露给A,因为A是当前图层,B是service,C是repository,D是context

关于代码的另外一点。有众所周知的命名约定。这些约定并不是我喜欢这个,你喜欢这样,但是你正在使用的框架严格遵循这些约定,因此使用另一个将命名约定与框架命名约定混合使你的代码看起来很混乱。

对不起,如果这只是一些示例代码,可以使您的代码结构清晰。我只需要描述这段代码的错误。


现在回答你真正的问题。您从相关问题中引用的方法在您的情况下不起作用。我认为只有从数据库加载订阅才会起作用。原因是引用的方法使用EntityKey(内部或直接)从上下文获取实体,但您的新实体尚未拥有实体键。我希望您的实体调用TryGetObjectStateEntry始终返回Detached。它是在附加过程中创建的实体键,或者您必须手动构建它。

如果你想要一些IsAttachedTo方法,试试这个:

public bool IsAttachedTo<T>(this ObjectContext context, T entity) where T : IEntity
{
    return context.GetObjectStateEntries(~EntityState.Detached)
                  .Where(e => !e.IsRelationship)
                  .Select(e => e.Entity)
                  .OfType<T>()
                  .Any(e => e.Id == entity.Id);
}

确保您的实体实现帮助程序接口

public interface IEntity
{
    int Id { get; } 
} 

但是为了能够分离附加的实体,你需要:

public T GetAttached<T>(this ObjectContext context, T entity) where T : IEntity
{
    return context.GetObjectStateEntries(~EntityState.Detached)
                  .Where(e => !e.IsRelationship)
                  .Select(e => e.Entity)
                  .OfType<T>()
                  .SingleOrDefault(e => e.Id == entity.Id);
}

您必须分离从此方法返回的实例。

无论如何,我会开始思考你为什么首先需要它,因为看起来你的架构有另一个错误的概念。为什么不直接使用附加实体?如果您不使用它们,为什么还要保留它们的上下文?

答案 1 :(得分:1)

IsAttachedTo可能不会通过键(Id)进行比较,而是通过对象标识进行比较。因为为循环中的每个项创建一个新的Subscription,所以对象都是不同的实例。

由于您的Id集合中似乎有相同types的对象,但最终只希望将每个键添加一个对象到上下文中,您可以通过过滤掉首先重复:

var distinctTypes = types.Distinct();
foreach (string s in distinctTypes)
{
    Subscription subscription = new Subscription { Id = Int32.Parse(s) };

    service.repository._context.AttachTo("Subscriptions", subscription);
    horse.Subscriptions.Add(subscription);
}

这样每个键只应该有一个对象附加到上下文。