Можно ли проверить, прикреплен ли объект к контексту данных в Entity Framework?

86

Я получаю следующую ошибку при попытке прикрепить объект, который уже прикреплен к данному контексту, через context.AttachTo(...):

Объект с таким же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом.

Есть ли способ добиться чего-то вроде:

context.IsAttachedTo(...)

Ура!

Редактировать:

Метод расширения, описанный Джейсоном, близок, но он не работает в моей ситуации.

Я пытаюсь выполнить некоторую работу, используя метод, описанный в ответе на другой вопрос:

Как удалить одну или несколько строк из таблицы с помощью Linq to Entities * без * предварительного извлечения строк?

Мой код выглядит примерно так:

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

Это отлично работает, за исключением случаев, когда я делаю что-то еще для этого пользователя, когда использую тот же метод и пытаюсь прикрепить фиктивный Userобъект. Это не удается, потому что я ранее прикрепил этот фиктивный пользовательский объект. Как я могу это проверить?

Джошкомли
источник

Ответы:

57

Вот что у меня получилось, и это очень хорошо работает:

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(...)за исключением того, что вы можете каждый раз использовать трюк с идентификатором, который я цитировал выше. В итоге вы получаете либо прикрепленный ранее объект, либо прикрепленный ваш собственный объект. Вызов CreateEntityKeyконтекста гарантирует, что он будет приятным и универсальным и будет работать даже с составными ключами без дальнейшего кодирования (потому что EF уже может сделать это за нас!).

Джошкомли
источник
У меня была похожая проблема, и это решило мою - отлично, ура! +1
RPM1984
4
Было бы даже лучше, если бы строковый параметр был заменен функцией выбора для коллекции, к которой принадлежит объект.
Джаспер
1
Есть идеи, почему (T)entry.Entityиногда возвращает ноль?
Tr1stan
1
Я не могу понять, что мне нужно установить для entitySetName. Я получаю исключение. Я действительно расстроен, потому что все, что я хочу сделать, это удалить пользователя, которого я не хочу иметь дело с таким количеством скрытого бессмысленного взрыва моего приложения.
Джули
1
если Tуже есть IEntityWithKey, не можете ли вы просто использовать его entity.EntityKeyсвойство, а не восстанавливать его, или вам нужно угадать / предоставить EntitySetName?
drzaus
54

Более простой подход:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
источник
1
Это сработало для меня, просто мне пришлось использовать "EntityState.Detached" вместо просто "detached" ...
Марсело Мьяра
22
Хм пробовал Ваше решение, но для меня isDetached верно, но все равно та же ошибка, когда я пытаюсь Прикрепить запись к контексту
Prokurors
Отличная оценка. Даже это работало с моим общим репозиторием.
ATHER
5
@Prokurors Недавно я узнал, почему проверка Моша не всегда достаточна для предотвращения ошибки, заключается в том, что .Include()свойства навигации ed также пытаются прикрепить, когда вы вызываете .Attachили устанавливаете его EntityStateзначение EntityState.Unchanged- и они будут конфликтовать, если какой-либо из объектов ссылается на одно и то же лицо. Я не понял, как присоединить только базовую сущность, поэтому мне пришлось немного переработать проект, чтобы использовать отдельные контексты для каждой «бизнес-транзакции», как это было задумано. Отмечу это для будущих проектов.
Aske B.
18

Попробуйте этот метод расширения (он не протестирован и нестандартный):

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

Вы можете получить EntityKeyобъект из существующего экземпляра Userс помощью свойства User.EntityKey(из интерфейса IEntityWithKey).

Джейсон
источник
Это очень хорошо, но у меня не работает в моей ситуации ... Я уточню вопрос с подробностями. ps вы хотите, чтобы bool не логическое, а статическое, но кроме этого довольно классного метода расширения!
joshcomley
@joshcomley: я думаю, что вы можете решить эту проблему, используя перегрузку, TryGetObjectStateEntryкоторая принимает EntityKeyвместо object. Я отредактировал соответственно. Дайте мне знать, если это не поможет, и мы вернемся к чертежной доске.
Джейсон
Ах, только что увидел это - я работал над решением, изложенным в только что опубликованном ответе. +1 за вашу помощь и указатели !!
joshcomley
Любые идеи, почему я получаю сообщение об ошибке, подробности здесь stackoverflow.com/questions/6653050/…
Joper
6

Используя ключ сущности объекта, который вы пытаетесь проверить:

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

Доброта,

Дэн

Дэниел Эллиотт
источник
0

Это не дает прямого ответа на вопрос OP, но именно так я решил свой.

Это для тех, кто использует DbContextвместо ObjectContext.

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

Метод DbSet.Find :

Находит объект с заданными значениями первичного ключа. Если в контексте существует сущность с указанными значениями первичного ключа, она возвращается немедленно, без запроса в магазин. В противном случае в хранилище делается запрос сущности с заданными значениями первичного ключа, и эта сущность, если она найдена, присоединяется к контексту и возвращается. Если в контексте или хранилище не найдено ни одной сущности, возвращается значение null.

По сути, он возвращает прикрепленный объект данного, primaryKeyпоэтому вам просто нужно применить изменения к возвращаемому объекту, чтобы сохранить правильный экземпляр.

Лоуренс
источник