Чи можна перевірити, чи об’єкт вже приєднаний до контексту даних в 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(...)ви можете використовувати трюк ID, який я цитував вище кожного разу. У підсумку ви отримуєте або раніше прикріплений об’єкт, або ваш власний об’єкт. Виклик CreateEntityKeyконтексту переконуєсь, що він гарний і загальний і буде працювати навіть із складеними клавішами без подальшого кодування (адже EF це вже може зробити для нас!).


У мене була подібна проблема, і це просто вирішило мою - блискуче, ура! +1
RPM1984,

4
Було б навіть краще, коли параметр рядка замінено функцією селектора для колекції, до якої належить сутність.
Джаспер

1
Будь-яка ідея, чому (T)entry.Entityіноді повертає null?
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", а не просто "від'єднати" ...
Марсело М'яра

22
Хм-м, спробував Твоє рішення, але для мене isDetached - це правда, але все-таки та сама помилка, коли я намагаюся долучити запис до контексту
Prokurors

Відмінний апраох. Навіть це працювало з моїм загальним сховищем.
ATHER

5
@Prokurors Нещодавно я дізнався причину перевірки Моша є не завжди достатньо , щоб запобігти помилці, то , що .Include()«властивість навігації ред також спробувало прикріпити при виклику .Attachабо встановлює його EntityStateдо EntityState.Unchanged- і вони будуть конфліктувати , якщо якісь - кого з осіб , відноситься до та сама сутність. Я не зрозумів, як приєднати лише базову сутність, тому мені довелося трохи переробити проект, щоб використовувати окремі контексти для кожної "ділової транзакції", як це було розроблено. Я зазначу це для майбутніх проектів.
Аске Б.

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. Я відредагував відповідно. Повідомте мене, якщо це не допоможе, і ми повернемося до креслення.
jason

Ах це тільки що побачив - я працював над рішенням, викладеним у відповіді, яку я щойно розмістив. +1 за вашу допомогу та вказівки !!
joshcomley

Будь-які ідеї, чому я отримую помилку, деталі є тут stackoverflow.com/questions/6653050 / ...
Joper

6

За допомогою ключа сутності об’єкта, який ви намагаєтеся перевірити:

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

Доброта,

Ден


0

Це не відповідає безпосередньо на питання ОП, але саме так я вирішив своє.

Це для тих, хто використовує 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.