是否可以检查对象是否已经附加到实体框架中的数据上下文?

当试图通过 context.AttachTo(...)附加一个已经附加到给定上下文的对象时,我得到了以下错误:

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

有没有一种方法可以达到这样的效果:

context.IsAttachedTo(...)

干杯!

编辑:

Jason 描述的扩展方法很接近,但是它不适合我的情况。

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

如何使用 Linq to Entities * 删除表中的一行或多行,而不首先检索行?

我的代码有点像这样:

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

这种方法工作得很好,除了当我为该用户执行其他操作时,我使用相同的方法并尝试附加一个虚拟的 User对象。这将失败,因为我之前已经附加了该虚拟用户对象。我怎么查这个?

61504 次浏览

Try this extension method (this is untested and off-the-cuff):

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

Given the situation that you describe in your edit, you might need to use the following overload that accepts an EntityKey instead of an object:

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

To build an EntityKey in your situation, use the following as a guideline:

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

You can get the EntityKey from an existing instance of User by using the property User.EntityKey (from interface IEntityWithKey).

Using the entity key of the object you are trying to check:

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

Kindness,

Dan

Here's what I ended up with, which works very nicely:

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

You can call it as follows:

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

This works very nicely because it's just like context.AttachTo(...) except you can use the ID trick I cited above each time. You end up with either the object previously attached or your own object being attached. Calling CreateEntityKey on the context makes sure it's nice and generic and will work even with composite keys with no further coding (because EF can already do that for us!).

Edit, twelve years later (Dec 2021)... oof!

Here's what I use in 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;
}
}

Usage:

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

You could refactor this to work with the entity key but this is enough to get anyone going!

A simpler approach is:

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

This does not directly answer OPs question but this is how I solved mine.

This is for those who are using DbContext instead of ObjectContext.

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

DbSet.Find Method:

Finds an entity with the given primary key values. If an entity with the given primary key values exists in the context, then it is returned immediately without making a request to the store. Otherwise, a request is made to the store for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found in the context or the store, then null is returned.

Basically, it returns the attached object of the given primaryKey so you just need to apply the changes on the returned object to keep the right instance.