EF 4.0 IsAttachedTo扩展方法和错误具有相同密钥的对象已经存在 [英] EF 4.0 IsAttachedTo extension method and error An object with the same key already exists
问题描述
我收到一个错误
ObjectStateManager中已存在具有相同密钥的对象。
ObjectStateManager无法跟踪具有相同
键的多个对象。
在我googled之后,我发现 IsAttachedTo
扩展方法:
这是我的代码:
foreach(类型中的字符串)
{
订阅订阅=新订阅{Id = Int32.Parse(s)};
if(service.repository._context.IsAttachedTo(subscription))
service.repository._context.Detach(subscription);
service.repository._context.AttachTo(订阅,订阅); // error here
horse.Subscriptions.Add(subscription);
}
但是当在foreach循环中出现具有相同键的订阅时,扩展方法 IsAttachedTo
每次都返回false,它不会检测到已经有这样的实体附加了。结果我得到相同的错误:
ObjectStateManager中已经存在具有相同密钥的对象。
ObjectStateManager无法跟踪具有相同
键的多个对象。
为什么会这样?
我有一点代码对您的评论是因为您的示例代码吓倒我。
您可能已经阅读了很多关于花式设计模式和分层架构,并且您开始自己使用它们。不幸的是,你错过了要点。这是什么?
service.repository._context.XXX
如果不封装其逻辑,您为什么要打扰任何服务层或存储库层?为什么在服务上暴露存储库?没有人应该知道服务内部的实现?更糟糕的是,为什么在存储库上公开上下文?这损坏了存储库的全部内容!
编写高质量面向对象的代码有很多支持规则。其中一个规则称为 Demeter法案。你不必遵循每个规则,你也不必一直遵循规则,但是在分层架构的情况下,这个法律是必须的。
如果您有三层A - > B - > C,则层A可以调用B层上的方法,但不知道C并且无法达到方法。如果可以的话,它不是一个新层,而是与B相同,层A不需要通过B调用它,它可以直接调用它。
在你的例子中,你刚刚将D暴露给A,因为A是当前层,B是服务
,C是存储库
和D是上下文
。
还有一点关于你的代码有着名的命名约定。这些约定不是关于我喜欢这个,而是你喜欢的,而是关于你正在使用的框架严格遵循这些约定的事实,所以使用另外一个来混合你的命名约定和框架命名约定使你的代码看起来很乱。
我很抱歉,如果这只是一些使代码结构清晰的示例代码。我只需要描述这段代码的错误。
您从相关问题引用的方法将不适用于您的情况。我认为只有从数据库加载订阅,它才有效。原因是引用的方法使用 EntityKey
(内部或直接)从上下文获取实体,但是您的新实体还没有实体密钥。我希望为您的实体调用 TryGetObjectStateEntry
将始终返回 Detached
。实体键在附加时创建,或者您必须手动构建。
如果您想要一些 IsAttachedTo
方法尝试这个:
public bool IsAttachedTo< T>(此ObjectContext上下文,T实体)其中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;(此ObjectContext上下文,T实体)其中T:IEntity
{
返回上下文.GetObjectStateEntries(〜EntityState.Detached)
.Where(e =>!e.IsRelationship)
.Select(e => e.Entity)
.OfType< T>()
.SingleOrDefault(e => e.Id == entity.Id);
}
您将必须分离从此方法返回的实例。
无论如何,我会开始想,为什么你需要这个,因为它看起来像你的架构有另一个错误的概念。你为什么不直接使用附件?如果您不使用它们,为什么甚至可以保留上下文?
I was getting an error
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
After i googled it i found IsAttachedTo
extension method there:
Is is possible to check if an object is already attached to a data context in Entity Framework?
here is my code:
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);
}
But when the subscription with the same key appeared in the foreach loop the extension method IsAttachedTo
returning false every time, it is does not detect that there is already such entity attached. And in result i am getting the same error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Why it is could be?
What can i do to fix that?
I have little code review for you because your sample code scares me.
You probably read a lot about fancy design patterns and layered architectures and you started to use them yourselves. Unfortunately you missed the main point. What the hell is this?
service.repository._context.XXX
Why do you bother with any service layer or repository layer if they don't encapsulate their logic? Why do you expose repository on the service? Nobody should know about service internal implementation? Even worse why do you expose context on the repository? That spoiled the whole point of the repository!
There are a lot of supporting rules for writing high quality object oriented code. One of this rules is called Law of Demeter. You don't have to follow each rule, you also don't have to follow rules all the time but in case of layered architecture this law is a must.
If you have three layers A -> B -> C the layer A can call methods on the layer B but it doesn't know about C and it cannot reach its methods. If it can, it is not a new layer but it is the same layer as B and the layer A doesn't need to call it through B, it can call it directly.
In your example you have just exposed D to A because A is current layer, B is service
, C is repository
and D is context
.
One more points about your code. There are well known naming conventions. These conventions are not about I like this and you like that but about the fact that framework you are using follow these conventions strictly so using another one to mix your naming convention with framework naming convention make your code look messy.
I'm sorry, If this was only some example code to make your code structuring clear. I just needed to describe how wrong this code is.
Now to your real problem. The method you have referenced from the related question will not work in your case. I think it will work only if you load the subscription from the database. The reason is that the referenced method uses EntityKey
(either internally or directly) to get the entity from context but your new entity doesn't have the entity key yet. I expect that calling TryGetObjectStateEntry
for your entity will always return Detached
. Entity key it is created during attaching or you have to build it manually.
If you want some IsAttachedTo
method try this:
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);
}
And make sure that your entity implements helper interface
public interface IEntity
{
int Id { get; }
}
But to be able to detach attached entity you will need:
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);
}
You will have to detach instance returned from this method.
Anyway I would start to think why do you need that for the first place because it looks like your architecture has another wrong concept. Why don't you use attached entities directly? If you don't use them why do you even keep the context with them?
这篇关于EF 4.0 IsAttachedTo扩展方法和错误具有相同密钥的对象已经存在的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!