Можно ли проверить, прикреплен ли объект к контексту данных в 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);

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поэтому вам просто нужно применить изменения к возвращаемому объекту, чтобы сохранить правильный экземпляр.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.