Czy można sprawdzić, czy obiekt jest już dołączony do kontekstu danych w Entity Framework?

86

Otrzymuję następujący błąd podczas próby dołączenia obiektu, który jest już dołączony do danego kontekstu za pośrednictwem context.AttachTo(...):

Obiekt z tym samym kluczem już istnieje w ObjectStateManager. ObjectStateManager nie może śledzić wielu obiektów za pomocą tego samego klucza.

Czy istnieje sposób na osiągnięcie czegoś w rodzaju:

context.IsAttachedTo(...)

Twoje zdrowie!

Edytować:

Metoda rozszerzenia, którą przedstawił Jason, jest bliska, ale nie działa w mojej sytuacji.

Próbuję trochę popracować metodą opisaną w odpowiedzi na inne pytanie:

Jak usunąć jeden lub więcej wierszy z mojej tabeli za pomocą Linq to Entities * bez * pobierania wierszy w pierwszej kolejności?

Mój kod wygląda trochę tak:

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

Działa to dobrze, z wyjątkiem sytuacji, gdy robię coś innego dla tego użytkownika, gdzie używam tej samej metody i próbuję dołączyć Userobiekt zastępczy . Nie udaje się to, ponieważ wcześniej dołączyłem ten fikcyjny obiekt użytkownika. Jak mogę to sprawdzić?

joshcomley
źródło

Odpowiedzi:

57

Oto, na czym skończyłem, co działa bardzo ładnie:

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

Możesz to nazwać w następujący sposób:

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

Działa to bardzo ładnie, ponieważ jest tak, jak za context.AttachTo(...)każdym razem, z wyjątkiem tego, że możesz użyć sztuczki identyfikacyjnej, którą cytowałem powyżej. Skończysz z dołączonym wcześniej obiektem lub dołączonym własnym obiektem. Wywołanie CreateEntityKeykontekstu zapewnia, że ​​jest ładny i ogólny i będzie działał nawet z kluczami złożonymi bez dalszego kodowania (ponieważ EF może już to zrobić za nas!).

joshcomley
źródło
Miałem podobny problem, a to właśnie rozwiązało mój - wspaniale, na zdrowie! +1
RPM1984
4
Byłoby jeszcze lepiej, gdyby parametr ciągu został zastąpiony przez funkcję selektora kolekcji, do której należy jednostka.
Jasper
1
Masz jakiś pomysł, dlaczego (T)entry.Entityczasami zwraca wartość null?
Tr1stan
1
Nie mogę dowiedzieć się, do czego powinienem ustawić nazwę elementu entitySetName. Ciągle dostaję wyjątek. Jestem naprawdę sfrustrowany, ponieważ wszystko, czego chcę, to usunąć użytkownika, którego nie chcę mieć do czynienia z tak wieloma ukrytymi bezsensownymi wysadzeniami mojej aplikacji.
Julie,
1
jeśli Tjuż jest IEntityWithKey, czy nie możesz po prostu użyć jego entity.EntityKeywłaściwości, zamiast ją rekonstruować lub musisz zgadywać / podawać EntitySetName?
drzaus
54

Prostsze podejście to:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
źródło
1
To zadziałało dla mnie, po prostu musiałem użyć „EntityState.Detached” zamiast po prostu „detached” ...
Marcelo Myara
22
Hmm wypróbowałem Twoje rozwiązanie, ale dla mnie isDetached jest prawdą, ale nadal ten sam błąd, gdy próbuję dołączyć wpis do kontekstu
Prokurors
Doskonała ocena. Nawet działało z moim repozytorium ogólnym.
ATHER
5
@Prokurors Niedawno dowiedziałem się, dlaczego sprawdzenie Mosha nie zawsze wystarcza, aby zapobiec błędowi, jest to, że `` .Include()właściwości nawigacji ed są również próbowane, gdy wywołujesz .Attachlub ustawia je EntityStatena EntityState.Unchanged- i będą one sprzeczne, jeśli którakolwiek z jednostek odwołuje się do ten sam podmiot. Nie wymyśliłem, jak dołączyć tylko element bazowy, więc musiałem nieco przeprojektować projekt, aby użyć oddzielnych kontekstów dla każdej „transakcji biznesowej”, tak jak została zaprojektowana. Zanotuję to dla przyszłych projektów.
Aske B.
18

Wypróbuj tę metodę przedłużania (nie została przetestowana i jest poza mankietem):

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

Biorąc pod uwagę sytuację, którą opisujesz w swojej edycji, może być konieczne użycie następującego przeciążenia, które akceptuje obiekt EntityKeyzamiast:

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

Aby zbudować EntityKeyw swojej sytuacji, użyj następujących wskazówek:

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

Możesz pobrać EntityKeyz istniejącej instancji programu Userprzy użyciu właściwości User.EntityKey(z interfejsu IEntityWithKey).

Jason
źródło
To bardzo dobrze, ale nie działa w mojej sytuacji ... Zaktualizuję pytanie o szczegóły. ps chcesz, aby bool nie był logiczny i statyczny, ale inny niż ta niesamowita metoda rozszerzenia!
joshcomley
@joshcomley: Myślę, że możesz rozwiązać problem za pomocą przeciążenia, TryGetObjectStateEntryktóre akceptuje EntityKeyzamiast object. Odpowiednio zredagowałem. Daj mi znać, jeśli to nie pomoże, a wrócimy do deski kreślarskiej.
jason
Ach, właśnie to zobaczyłem - pracowałem nad rozwiązaniem opisanym w odpowiedzi, którą właśnie opublikowałem. +1 za pomoc i wskazówki !!
joshcomley
Wszelkie pomysły, dlaczego otrzymuję błąd, szczegóły są tutaj stackoverflow.com/questions/6653050/…
Joper,
6

Używając klucza encji obiektu, który próbujesz sprawdzić:

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

Życzliwość,

Dan

Daniel Elliott
źródło
0

To nie odpowiada bezpośrednio na pytanie PO, ale tak rozwiązałem swoje.

To jest dla tych, którzy używają DbContextzamiast ObjectContext.

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

Metoda DbSet.Find :

Znajduje jednostkę z podanymi wartościami klucza podstawowego. Jeśli w kontekście istnieje jednostka z podanymi wartościami klucza podstawowego, to jest zwracana natychmiast bez wysyłania żądania do sklepu. W przeciwnym razie do magazynu zostanie wysłane żądanie dotyczące jednostki z podanymi wartościami klucza podstawowego, a ta jednostka, jeśli zostanie znaleziona, zostanie dołączona do kontekstu i zwrócona. Jeśli w kontekście lub w sklepie nie zostanie znaleziona żadna jednostka, zwracana jest wartość null.

Zasadniczo zwraca dołączony obiekt danego podanego, primaryKeywięc wystarczy zastosować zmiany w zwróconym obiekcie, aby zachować właściwą instancję.

Lawrence
źródło