Entity Framework 4 - AddObject vs Attach

135

Ostatnio pracowałem z Entity Framework 4 i jestem nieco zdezorientowany, kiedy używać ObjectSet.Attach i ObjectSet.AddObject .

Z mojego zrozumienia:

  • Użyj opcji „Dołącz”, gdy jednostka już istnieje w systemie
  • Użyj „AddObject” podczas tworzenia zupełnie nowej jednostki

Tak więc, jeśli tworzę nową osobę , robię to.

var ctx = new MyEntities();
var newPerson = new Person { Name = "Joe Bloggs" };
ctx.Persons.AddObject(newPerson);
ctx.SaveChanges();

Jeśli modyfikuję istniejącą osobę , robię to:

var ctx = new MyEntities();
var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
existingPerson.Name = "Joe Briggs";
ctx.SaveChanges();

Pamiętaj, że to bardzo prosty przykład. W rzeczywistości używam Pure POCO (bez generowania kodu), wzorca repozytorium (nie zajmuję się ctx.Persons) i jednostki pracy (nie zajmuję się ctx.SaveChanges). Ale „pod kołdrą”, powyższe dzieje się w mojej realizacji.

Teraz moje pytanie - nie znalazłem jeszcze scenariusza, w którym musiałem użyć Attach .

Czego tu brakuje? Kiedy musimy używać Attach?

EDYTOWAĆ

Aby wyjaśnić, szukam przykładów, kiedy używać Attach zamiast AddObject (lub odwrotnie).

EDYCJA 2

Poniższa odpowiedź jest poprawna (którą zaakceptowałem), ale pomyślałem, że dodam kolejny przykład, w którym Attach byłby przydatny.

W moim powyższym przykładzie dotyczącym modyfikowania istniejącej osoby faktycznie wykonywane są dwa zapytania.

Jeden do pobierania osoby (.SingleOrDefault), a drugi do wykonywania AKTUALIZACJI (.SaveChanges).

Jeśli (z jakiegoś powodu) już wiedziałem, że „Joe Bloggs” istnieje w systemie, po co dodatkowe zapytanie, aby go znaleźć? Mógłbym to zrobić:

var ctx = new MyEntities();
var existingPerson = new Person { Name = "Joe Bloggs" };
ctx.Persons.Attach(existingPerson);
ctx.SaveChanges();

Spowoduje to wykonanie tylko instrukcji UPDATE.

RPM1984
źródło
Dołącz jest również używany w MVC teraz dni podczas umieszczania modeli z powrotem bezpośrednio do EF. Działa całkiem nieźle i oszczędza mnóstwo linii kodu.
Piotr Kula

Odpowiedzi:

165

ObjectContext.AddObject i ObjectSet.AddObject :
Metoda AddObject służy do dodawania nowo utworzonych obiektów, które nie istnieją w bazie danych. Encja otrzyma automatycznie wygenerowany tymczasowy EntityKey, a jej EntityState zostanie ustawiony na Dodano . Po wywołaniu SaveChanges będzie jasne dla EF, że ta jednostka musi zostać wstawiona do bazy danych.

ObjectContext.Attach i ObjectSet.Attach :
Z drugiej strony, Attach jest używany dla jednostek, które już istnieją w bazie danych. Zamiast ustawiać EntityState na Added, Attach skutkuje niezmienionym EntityState, co oznacza, że ​​nie zmienił się od czasu dołączenia do kontekstu. Zakłada się, że dołączane obiekty istnieją w bazie danych. Jeśli zmodyfikujesz obiekty po ich dołączeniu, po wywołaniu SaveChanges wartość EntityKey zostanie użyta do zaktualizowania (lub usunięcia) odpowiedniego wiersza poprzez znalezienie pasującego identyfikatora w tabeli db.

Ponadto za pomocą metody Attach można zdefiniować relacje między jednostkami, które już istnieją w ObjectContext, ale mająnie został podłączony automatycznie. Zasadniczo głównym celem Attach jest połączenie jednostek, które są już dołączone do ObjectContext i nie sąnowe, więc nie można używać Attach do dołączania jednostek, których EntityState jest dodana. W takim przypadku musisz użyć Add () .

Na przykład załóżmy, że Twoja encja Person ma właściwość nawigacji o nazwie Adresy, która jest zbioremencji Adres . Powiedzmy, że przeczytałeś oba obiekty z kontekstu, ale nie są one ze sobą powiązane i chcesz, aby tak było:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.PersonReference.Attach(existingPerson)
ctx.SaveChanges();
Morteza Manavi
źródło
Dzięki za odpowiedź, rozumiem definicję tych dwóch (czyli pierwsze dwa akapity). Ale nie rozumiem scenariusza, w którym MUSZĘ używać Attach. Twój ostatni akapit nie ma dla mnie sensu (czyta się tak, jakby był połączeniem dwóch pierwszych akapitów), czy możesz podać przykład, gdzie użyłbym opcji „Dołącz” w powyższym scenariuszu? Właśnie tego szukam - przykładów, a nie definicji. Ale naprawdę doceniam twój czas. :)
RPM1984
1
Nie ma problemu, dodałem fragment kodu, aby wyjaśnić ostatni akapit, jak widać, mamy 2 niepowiązane ze sobą obiekty, a Dołącz pomaga nam je ze sobą powiązać. Innym przykładem byłoby użycie Attach () metodę Dołączanie „Indywidualny podmiot” powrót do kontekstu (Istnieją różne powody, dla których warto jednorodzinnego jednostka będzie do tyłu Dołączone do kontekstu)
Morteza Manavi
1
Tak, teraz mam cię. Właśnie obejrzałem film TechEd na EF4 (Julie Lerman), który pokazał przykład. Możesz mieć jednostkę, której NIE pobrałeś z zapytania (tj. Jest odłączona), ale wiesz, że istnieje, dlatego używasz polecenia Attach, aby wykonać AKTUALIZACJĘ tej encji. Ma to sens, chociaż wciąż staram się wyobrazić sobie scenariusz, w którym istniałaby „odłączona” jednostka. Dzięki za pomoc.
RPM1984
1
Świetny. Czy mogę prosić o udostępnienie linku do filmu innym programistom, którzy mogliby przeczytać ten post?
Morteza Manavi
3
Powyższe łącze udostępnione przez RPM1984 jest zepsute, jest teraz przekierowywane do channel9.msdn.com/Events/TechEd/NorthAmerica/2010/DEV205 . Enjoy
Stacked
32

To jest późna odpowiedź, ale może pomóc innym, którzy ją znajdą.

Zasadniczo „odłączony” obiekt może się zdarzyć, gdy manipulujesz bytem poza zakresem „używania”.

Employee e = null;

using (var ctx = new MyModelContainer())
{
     e = ctx.Employees.SingleOrDefault(emp => emp .....);
}

using (var ctx2 = new MyModelContainer())
{
     e; // This entity instance is disconnected from ctx2
}

Jeśli wprowadzisz inny zakres „używający”, zmienna „e” zostanie odłączona, ponieważ należy do poprzedniego zakresu „używającego”, a ponieważ poprzedni zakres „używający” zostanie zniszczony, wówczas „e” zostanie odłączony.

Tak to rozumiem.

TchiYuan
źródło
3
Przykład Tchi jest doskonałym i prostym przykładem - tak, zmienną Employee należy zadeklarować na zewnątrz. wypróbuj e.Address.Street poza zakresem i zobacz wyskakujące okienko z wyjątkiem zerowego odwołania. Jeśli dołączysz, aplikacja nie będzie musiała wracać do bazy danych dla pracownika w drugim zakresie.
Steve
10

To jest cytat z Programming Entity Framework: DbContext

Wywołanie funkcji Remove w jednostce, która nie jest śledzona przez kontekst, spowoduje zgłoszenie InvalidOperationException. Entity Framework zgłasza ten wyjątek, ponieważ nie jest jasne, czy jednostka, którą próbujesz usunąć, jest istniejącą jednostką, która powinna zostać oznaczona do usunięcia, czy nową jednostką, którą należy po prostu zignorować. Z tego powodu nie możemy po prostu użyć Usuń, aby oznaczyć odłączoną jednostkę jako usuniętą; musimy to najpierw załączyć .

private static void TestDeleteDestination()
{
    Destination canyon;
    using (var context = new BreakAwayContext())
    {
        canyon = (from d in context.Destinations
        where d.Name == "Grand Canyon"
        select d).Single();
    }
    DeleteDestination(canyon);
}
private static void DeleteDestination(Destination destination)
{
    using (var context = new BreakAwayContext())
    {
        context.Destinations.Attach(destination);
        context.Destinations.Remove(destination);
        context.SaveChanges();
    }
}

Metoda TestDeleteDestination symuluje aplikację kliencką pobierającą istniejącą lokalizację docelową z serwera, a następnie przekazującą ją do metody DeleteDestination na serwerze. Metoda DeleteDestination używa metody Attach, aby poinformować kontekst, że jest to istniejący Destination. Następnie metoda Remove jest używana do zarejestrowania istniejącego miejsca docelowego do usunięcia

Teoman shipahi
źródło
-8

A co powiesz na odwoływanie się tylko do klucza podstawowego zamiast dołączania?

to znaczy:

var existingPerson = ctx.Persons.SingleOrDefault(p => p.Name = "Joe Bloggs" };
var myAddress = ctx.Addresses.First(a => a.PersonID != existingPerson.PersonID);
existingPerson.AddressId = myAddress.Id // not -> existingPerson.Addresses.Attach(myAddress);
// OR:
myAddress.Person.Id = existingPerson.Id // not -> myAddress.PersonReference.Attach(existingPerson);
ctx.SaveChanges();
Dan
źródło