Entity Framework - dodawanie / import wielu rekordów naraz
Od czasu do czasu musimy wstawić do bazy wiele rekordów naraz, najczęściej w przypadku importu/aktualizacji danych. Jest wiele sposobów na wykonanie tej czynności, ale najprościej stworzyć DbContext, dodać elementy do kolekcji, a następnie zapisać za pomocą SaveChanges. Gdy korzystamy z Entity Framework 6, istnieje kilka zasad, których powinniśmy przestrzegać, aby wydajnośc rozwiązania była przyzwoita:
Przykładowy kod korzystający z tych zasad:
Metoda Batch source, która dzieli rekordy na partie:
W następnym wpisie sprawdzę czy te reguły działają też w Entity Framework Core.
- Ustawienie context.Configuration.AutoDetectChangesEnabled na false. Domyślnie Entity Framework używa wykrywacza zmian, aby wygenerować odpowiednie skrypty SQL przy zapisywaniu zmian. Gdy dodajemy rekordy do bazy, to wiemy, że nie ma zmian. Nie ma potrzebu zatrudniania wykrywacza, który w EF 6 zużywa sporo zasobów. Trzeba go wyłączyć.
- Dzielenie danych na partie.Jeżeli chcemy wstawić do bazy np. 10000 rekordów, lepiej podzielić je na partie i wywoływać SaveChanges() po dodaniu każdej z nich. Jaka powinna być wielkość jednej partii? Może być np. 100, ale to trzeba wydedukować samemu, bo zależy też od wielkości danych.
- Tworzenie nowego DbContextu po każdym zapisie. Nawet jeżeli zapisujemy zmiany po każdej partii, dobrym pomysłem jest tworzenie nowego DbContextu po każdym zapisie. Poprzedni DbContext zostanie zwolniony, a z nim kolekcja obiektów już dodanych. Koszty wstawienia do pustej kolekcji są niższe niż do kolekcji, w której znajduje się tysiące obiektów.
- Użycie DbSet.AddRange (dokumentacja). Nowość w EF 6. To jest odmienne podejście do wykrywacza zmian. Domyślnie, gdy dodajemy rekordy jeden po jednym, wykrywacz zmian wykonuje porównania po każdym dodaniu. Jeżeli dodamy całą partię na jeden raz, wykrywanie zmian będzie też szybsze.
- Wstawianie danych w wielu wątkach. Ten punkt zależy mocno od wydajności dysków, na których składowana jest baza. W przypadku SSD wynik może być niezły, w przypadku dysków talerzowych wręcz przeciwnie. Wszystko należy przetestować.
- Ustawienie context.Configuration.ValidateOnSaveEnabled na false. To jest opcjonalne , bo wyłącza wbudowaną w EF walidację. Ale jeśli mamy pewność, że nasze obiekty są poprawne, możemy oszczędzić trochę czasu.
Przykładowy kod korzystający z tych zasad:
Metoda Batch source, która dzieli rekordy na partie:
W następnym wpisie sprawdzę czy te reguły działają też w Entity Framework Core.
Hej. Spoko info, ale mam jedno pytanie. Czy speawdzales moze jaki sql jest generowany? Insert into, czy może setki insertów?
ReplyDeleteSetki insertów. Dopiero EF Core potrafi zrobić to w jednym kroku.
DeleteTo kiepsko. Już się ucieszyłem, że potrafi to zgrupować. Choćby na zasadzie:
DeleteINSERT INTO MojaTabela
SELECT ...
UNION ALL SELECT ...
UNION ALL SELECT ...
Skoro to setki insertów to nie wiem do końca jaki jest z tego zysk. Wydaje mi się, że w większości wypadków największe opóźnienia są właśnie na etapie wykonywania operacji w bazie danych (wliczając w to opóźnienia komunikacji sieciowej). To co robimy po stronie kodu nie ma większego znaczenia, chyba że operujemy na grubych milionach rekordów.
Niestety prawda jest taka, że do masowych importów najlepiej cofnąć się do czystych SqlCommand lub wręcz BULK IMPORT.
Nie, nie. Wstawianie w SQL jest tutaj najmniejszym problemem. SQL Server radzi sobie doskonale z takimi wstawieniami. Jeżeli na tabelach nie ma triggerów z dodatkową logiką, nie powinno być źle.
DeleteChange tracker w Entity Framework jest na tyle wolny, że jego wyłączenie powoduje największą różnicę. Oczywiście można cofać się do SqlCommand, BULK IMPORT lub użyć np. Entity Framework Extensions (http://entityframework-extensions.net/), ale nie zawsze jest taka potrzeba. Czasami po prostu ustawić kilka opcji.
Musiałem z bazy plikowej przenieść dane do mssql (około 60 tyś wierszy). Trwało to około 2h. Zastosowałem radę #1, #2, i #4 i czas się zmniejszył do 1 minuty. Nie sądziłem że aż taki efekt zmian w kodzie będzie.
ReplyDeleteDzięki.
Good bless
ReplyDelete