Pobieram dane filmu z zewnętrznego interfejsu API. W pierwszej fazie zeskrobuję każdy film i wstawię go do własnej bazy danych. W drugiej fazie będę okresowo aktualizować moją bazę danych za pomocą interfejsu API „Zmiany” interfejsu API, który mogę zapytać, aby zobaczyć, które filmy zostały zmienione.
Moja warstwa ORM to Entity-Framework. Klasa Movie wygląda następująco:
class Movie
{
public virtual ICollection<Language> SpokenLanguages { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
Problem pojawia się, gdy mam film, który wymaga aktualizacji: moja baza danych będzie myślała o śledzonym obiekcie, a nowy, który otrzymam z wywołania API aktualizacji, to różne obiekty, niezależnie od tego .Equals()
.
Powoduje to problem, ponieważ gdy teraz spróbuję zaktualizować bazę danych o zaktualizowany film, wstawi go zamiast aktualizować istniejący film.
Miałem już ten problem z językami i moim rozwiązaniem było wyszukiwanie załączonych obiektów językowych, odłączenie ich od kontekstu, przeniesienie ich PK do zaktualizowanego obiektu i dołączenie go do kontekstu. Kiedy SaveChanges()
jest teraz wykonywany, zasadniczo go zastąpi.
Jest to dość śmierdzące podejście, ponieważ jeśli będę kontynuować to podejście do mojego Movie
obiektu, oznacza to, że będę musiał odłączyć film, języki, gatunki i słowa kluczowe, wyszukać każdy z nich w bazie danych, przenieść ich identyfikatory i wstawić nowe obiekty.
Czy można to zrobić bardziej elegancko? Idealnie chcę po prostu przekazać zaktualizowany film do kontekstu i wybrać ten właściwy film do aktualizacji na podstawie Equals()
metody, zaktualizować wszystkie jego pola i dla każdego złożonego obiektu: ponownie użyć istniejącego rekordu na podstawie własnej Equals()
metody i wstawić, jeśli jeszcze nie istnieje.
Mogę pominąć odłączanie / dołączanie, podając .Update()
metody dla każdego złożonego obiektu, których mogę użyć w połączeniu z odzyskiwaniem wszystkich dołączonych obiektów, ale nadal będzie to wymagało ode mnie pobrania każdego istniejącego obiektu w celu jego aktualizacji.
źródło
id
a filmy z zewnętrznego interfejsu API są dopasowywane do lokalnych za pomocą polatmdbid
. Nie mogę pobrać wszystkich elementów, które wymagają aktualizacji w jednym połączeniu, ponieważ dotyczą one filmów, gatunków, języków, słów kluczowych itp. Każdy z nich ma PK i może już istnieć w bazie danych.Odpowiedzi:
Nie znalazłem tego, na co liczyłem, ale znalazłem poprawę w stosunku do istniejącej sekwencji select-detach-update-attach.
Metoda rozszerzenia
AddOrUpdate(this DbSet)
pozwala robić dokładnie to, co chcę: wstawić, jeśli go nie ma, i zaktualizować, jeśli znalazła istniejącą wartość. Nie zdawałem sobie sprawy z używania tego wcześniej, ponieważ tak naprawdę widziałem, że można go stosować tylko wseed()
metodzie w połączeniu z Migracjami. Jeśli jest jakiś powód, dla którego nie powinienem tego używać, daj mi znać.Coś użytecznego do odnotowania: Dostępne jest przeciążenie, które pozwala dokładnie wybrać sposób ustalenia równości. Tutaj mogłem użyć mojego,
TMDbId
ale zamiast tego zdecydowałem się po prostu całkowicie zignorować mój własny identyfikator i zamiast tego użyć PK na TMDbId w połączeniu zDatabaseGeneratedOption.None
. W stosownych przypadkach stosuję to podejście do każdej podkolekcji.Ciekawa część źródła :
tak właśnie dane są aktualizowane pod maską.
Pozostaje tylko wywoływanie
AddOrUpdate
każdego obiektu, na który chcę mieć wpływ:Nie jest tak czysty, jak się spodziewałem, ponieważ muszę ręcznie określić każdy element mojego obiektu, który należy zaktualizować, ale jest tak blisko, jak to możliwe.
Powiązana lektura: /programming/15336248/entity-framework-5-updating-a-record
Aktualizacja:
Okazuje się, że moje testy nie były wystarczająco rygorystyczne. Po użyciu tej techniki zauważyłem, że podczas dodawania nowego języka nie był on połączony z filmem. w tabeli wiele do wielu. Jest to znany, ale pozornie niski priorytet, o ile mi wiadomo.
W końcu zdecydowałem się na podejście, w którym mam
Update(T)
metody dla każdego typu i śledzę następującą sekwencję zdarzeń:Update()
metody, aby zaktualizować go o nowe wartościTo dużo pracy ręcznej i jest brzydkie, więc przejdzie jeszcze kilka refaktoryzacji, ale teraz moje testy wskazują, że powinno działać w bardziej rygorystycznych scenariuszach.
Po dalszym oczyszczeniu używam teraz tej metody:
To pozwala mi tak to nazwać i wstawić / zaktualizować podstawowe kolekcje:
Zauważ, jak ponownie przypisuję odzyskaną wartość do oryginalnego obiektu głównego: teraz jest on połączony z każdym dołączonym obiektem. Aktualizacja obiektu głównego (filmu) odbywa się w ten sam sposób:
źródło
Ponieważ mamy do czynienia z różnych dziedzin
id
itmbid
uważam, że aktualizowanie API do jednolitego i oddzielny indeks wszystkich informacji jak gatunków, języków, słowa kluczowe itp ... A potem zatelefonować do indeksu i sprawdzić, zamiast gromadzenia informacji wszystkie informacje o konkretnym obiekcie w klasie filmu.źródło