Entity Framework 4, obiekty POCO i ASP.Net MVC2. Mam wiele relacji, powiedzmy między podmiotami BlogPost i Tag. Oznacza to, że w mojej wygenerowanej przez T4 klasie POCO BlogPost mam:
public virtual ICollection<Tag> Tags {
// getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;
Proszę o BlogPost i powiązane Tagi z instancji ObjectContext i wysyłam je do innej warstwy (Widok w aplikacji MVC). Później otrzymuję zaktualizowany BlogPost ze zmienionymi właściwościami i zmienionymi relacjami. Na przykład miał tagi „A”, „B” i „C”, a nowe tagi to „C” i „D”. W moim przykładzie nie ma nowych tagów, a właściwości tagów nigdy się nie zmieniają, więc jedyną rzeczą, którą należy zapisać, są zmienione relacje. Teraz muszę zapisać to w innym ObjectContext. (Aktualizacja: teraz próbowałem to zrobić w tej samej instancji kontekstowej i również się nie udało).
Problem: nie mogę sprawić, żeby to właściwie uratowało relacje. Próbowałem wszystkiego, co znalazłem:
- Controller.UpdateModel i Controller.TryUpdateModel nie działają.
- Pobieranie starego BlogPost z kontekstu, a następnie modyfikowanie kolekcji nie działa. (różnymi metodami od następnego punktu)
- To prawdopodobnie zadziała, ale mam nadzieję, że to tylko obejście, a nie rozwiązanie :(.
- Wypróbowano funkcje Attach / Add / ChangeObjectState dla BlogPost i / lub Tagi we wszystkich możliwych kombinacjach. Niepowodzenie.
- To wygląda co muszę, ale to nie działa (próbowałem to naprawić, ale nie mogę do mojego problemu).
- Wypróbowano ChangeState / Add / Attach / ... obiekty relacji kontekstu. Niepowodzenie.
„Nie działa” oznacza w większości przypadków, że pracowałem nad danym „rozwiązaniem”, dopóki nie wygeneruje ono żadnych błędów i nie zapisze przynajmniej właściwości BlogPosta. To, co dzieje się z relacjami, jest różne: zwykle Tagi są ponownie dodawane do tabeli Tagów z nowymi PK, a zapisany BlogPost odwołuje się do nich, a nie do oryginalnych. Oczywiście zwrócone tagi mają PK, a przed metodami save / update sprawdzam PK i są one równe tym w bazie danych, więc prawdopodobnie EF myśli, że są to nowe obiekty, a te PK to tymczasowe.
Problem, o którym wiem i może uniemożliwić znalezienie zautomatyzowanego prostego rozwiązania: Kiedy kolekcja obiektu POCO zostanie zmieniona, powinno to nastąpić przez wspomnianą powyżej właściwość kolekcji wirtualnej, ponieważ wtedy sztuczka FixupCollection zaktualizuje odwrotne odwołania na drugim końcu relacji wiele-do-wielu. Jednak gdy widok „zwraca” zaktualizowany obiekt BlogPost, tak się nie stało. Oznacza to, że może nie ma prostego rozwiązania mojego problemu, ale to by mnie bardzo zasmuciło i nienawidziłbym triumfu EF4-POCO-MVC :(. To również oznaczałoby, że EF nie może tego zrobić w środowisku MVC cokolwiek Używane są typy obiektów EF4 :(. Myślę, że śledzenie zmian oparte na migawkach powinno wykryć, że zmieniony BlogPost ma powiązania z tagami z istniejącymi PK.
Btw: Myślę, że ten sam problem występuje w relacjach jeden do wielu (Google i mój kolega tak twierdzą). Spróbuję w domu, ale nawet jeśli to zadziała, nie pomoże mi to w moich sześciu relacjach wiele do wielu w mojej aplikacji :(.
źródło
Odpowiedzi:
Spróbujmy w ten sposób:
Edytować:
Myślę, że jeden z moich komentarzy dał ci fałszywą nadzieję, że EF dokona dla ciebie scalenia. Dużo bawiłem się tym problemem i mój wniosek mówi, że EF nie zrobi tego za Ciebie. Myślę, że znalazłeś również moje pytanie na MSDN . W rzeczywistości w Internecie jest mnóstwo takich pytań. Problem w tym, że nie jest jasno określone, jak sobie poradzić z tym scenariuszem. Spójrzmy więc na problem:
Tło problemu
EF musi śledzić zmiany w jednostkach, aby trwałość wiedział, które rekordy muszą zostać zaktualizowane, wstawione lub usunięte. Problem polega na tym, że za śledzenie zmian odpowiedzialny jest ObjectContext. ObjectContext może śledzić zmiany tylko dla dołączonych jednostek. Jednostki, które są tworzone poza ObjectContext, nie są w ogóle śledzone.
Opis problemu
Na podstawie powyższego opisu możemy wyraźnie stwierdzić, że EF jest bardziej odpowiedni dla połączonych scenariuszy, w których jednostka jest zawsze dołączona do kontekstu - typowego dla aplikacji WinForm. Aplikacje internetowe wymagają scenariusza rozłączenia, w którym kontekst jest zamykany po przetworzeniu żądania, a zawartość jednostki jest przekazywana jako odpowiedź HTTP do klienta. Kolejne żądanie HTTP dostarcza zmodyfikowaną zawartość jednostki, która musi zostać odtworzona, dołączona do nowego kontekstu i utrwalona. Rekreacja zwykle odbywa się poza zakresem kontekstu (architektura warstwowa z uporczywą ignorancją).
Rozwiązanie
Jak więc poradzić sobie z takim oderwanym scenariuszem? Korzystając z klas POCO mamy 3 sposoby radzenia sobie ze śledzeniem zmian:
Ręczna synchronizacja na pojedynczym obiekcie jest łatwym zadaniem. Wystarczy dołączyć jednostkę i wywołać AddObject w celu wstawienia, DeleteObject w celu usunięcia lub ustawienia stanu w ObjectStateManager na Modified w celu aktualizacji. Prawdziwy ból pojawia się, gdy mamy do czynienia z grafem obiektowym zamiast pojedynczej jednostki. Ten ból jest jeszcze gorszy, gdy masz do czynienia z niezależnymi stowarzyszeniami (takimi, które nie używają własności klucza obcego) i wieloma powiązaniami. W takim przypadku musisz ręcznie zsynchronizować każdą jednostkę w grafie obiektów, ale także każdą relację w grafie obiektów.
Synchronizacja ręczna jest proponowana jako rozwiązanie w dokumentacji MSDN: Dołączanie i odłączanie obiektów mówi:
Wspomniane metody to ChangeObjectState i ChangeRelationshipState of ObjectStateManager = ręczne śledzenie zmian. Podobna propozycja znajduje się w innym artykule dokumentacji MSDN: Definiowanie i zarządzanie relacjami mówi:
Ponadto istnieje wpis na blogu dotyczący EF v1, który krytykuje dokładnie to zachowanie EF.
Powód rozwiązania
EF ma wiele "pomocnych" operacji i ustawień, takich jak Refresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOption itp. Jednak według moich badań wszystkie te funkcje działają tylko dla pojedynczej encji i wpływają tylko na właściwości skalarne (= nie właściwości nawigacji i relacje). Raczej nie testuję tych metod ze złożonymi typami zagnieżdżonymi w encji.
Inne proponowane rozwiązanie
Zamiast prawdziwej funkcjonalności scalania, zespół EF zapewnia coś, co nazywa się jednostkami samośledzącymi (STE), które nie rozwiązują problemu. Przede wszystkim STE działa tylko wtedy, gdy ta sama instancja jest używana do całego przetwarzania. W przypadku aplikacji webowej tak nie jest, chyba że przechowujesz instancję w stanie widoku lub sesji. Z tego powodu jestem bardzo niezadowolony z używania EF i zamierzam sprawdzić cechy NHibernate. Pierwsza obserwacja mówi, że NHibernate może mieć taką funkcjonalność .
Wniosek
Skończyłem na tym założeniu jednym linkiem do innego pokrewnego pytania na forum MSDN. Sprawdź odpowiedź Zeeshana Hirani. Jest autorem Entity Framework 4.0 Recipes . Jeśli mówi, że automatyczne scalanie grafów obiektów nie jest obsługiwane, wierzę mu.
Ale nadal istnieje możliwość, że całkowicie się mylę i istnieją pewne funkcje automatycznego scalania w EF.
Edycja 2:
Jak widać, zostało to już dodane do MS Connect jako sugestia w 2007 roku. MS zamknęło to jako coś do zrobienia w następnej wersji, ale tak naprawdę nic nie zostało zrobione, aby poprawić tę lukę poza STE.
źródło
Mam rozwiązanie problemu, które opisał wyżej Ladislav. Utworzyłem metodę rozszerzenia dla DbContext, która automatycznie wykona dodawanie / aktualizację / usuwanie na podstawie różnicy podanego wykresu i utrwalonego wykresu.
Sprawdź, czy może pomóc http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- wykres-odłączonych-bytów /
Możesz przejść bezpośrednio do kodu tutaj https://github.com/refactorthis/GraphDiff
źródło
Wiem, że jest późno na OP, ale ponieważ jest to bardzo częsty problem, opublikowałem to na wypadek, gdyby służyło komuś innemu. Bawiłem się tym problemem i myślę, że znalazłem dość proste rozwiązanie, co robię:
W poniższym przykładzie „dataobj” i „_categories” to parametry odebrane przez mój kontroler, „dataobj” to mój główny obiekt, a „_categories” to IEnumerable zawierające identyfikatory kategorii wybranych przez użytkownika w widoku.
Działa nawet dla wielu relacji
źródło
Zespół Entity Framework zdaje sobie sprawę, że jest to problem z użytecznością i planuje rozwiązać go po EF6.
Od zespołu Entity Framework:
Jeśli ma to na Ciebie wpływ, zagłosuj na tę funkcję pod adresem
http://entityframework.codeplex.com/workitem/864
źródło
Wszystkie odpowiedzi świetnie wyjaśniły problem, ale żadna z nich tak naprawdę nie rozwiązała problemu.
Odkryłem, że jeśli nie użyję relacji w encji nadrzędnej, ale po prostu dodam i usunę jednostki podrzędne, wszystko działa dobrze.
Przepraszam za VB, ale w tym właśnie jest napisany projekt, nad którym pracuję.
Jednostka nadrzędna „Report” ma relację jeden do wielu z „ReportRole” i ma właściwość „ReportRoles”. Nowe role są przekazywane przez oddzielony przecinkami ciąg z wywołania Ajax.
Pierwsza linia usunie wszystkie jednostki podrzędne, a jeśli użyję „report.ReportRoles.Remove (f)” zamiast „db.ReportRoles.Remove (f)”, otrzymam błąd.
źródło