Szukam najszybszego sposobu wstawienia do Entity Framework.
Pytam o to ze względu na scenariusz, w którym masz aktywny TransactionScope, a wstawienie jest ogromne (4000+). Może to potencjalnie trwać dłużej niż 10 minut (domyślny limit czasu transakcji), co doprowadzi do niekompletnej transakcji.
c#
sql
entity-framework
Bongo Sharp
źródło
źródło
Odpowiedzi:
Do Twojej uwagi w komentarzach do twojego pytania:
To najgorsze, co możesz zrobić! Wezwanie
SaveChanges()
do każdego rekordu spowalnia bardzo duże wkładki luzem. Zrobiłbym kilka prostych testów, które prawdopodobnie poprawią wydajność:SaveChanges()
raz po WSZYSTKICH rekordach.SaveChanges()
po na przykład 100 rekordów.SaveChanges()
na przykład 100 rekordów i usuń kontekst i utwórz nowy.W przypadku wkładek luzem pracuję i eksperymentuję z takim wzorem:
Mam program testowy, który wstawia 560 000 jednostek (9 właściwości skalarnych, brak właściwości nawigacyjnych) do DB. Z tym kodem działa w mniej niż 3 minuty.
Dla wykonania ważne jest, aby wywoływać
SaveChanges()
po „wielu” rekordach („wielu” około 100 lub 1000). Poprawia także wydajność, aby pozbyć się kontekstu po SaveChanges i utworzyć nowy. To usuwa kontekst ze wszystkich entites,SaveChanges
nie robi tego, byty są nadal dołączone do kontekstu w stanieUnchanged
. Rosnące rozmiary dołączonych elementów w kontekście spowalniają wstawianie krok po kroku. Pomocne jest więc wyczyszczenie go po pewnym czasie.Oto kilka pomiarów dla moich 560000 podmiotów:
Zachowanie w pierwszym powyższym teście polega na tym, że wydajność jest bardzo nieliniowa i z czasem bardzo spada. („Wiele godzin” to oszacowanie, nigdy nie ukończyłem tego testu, po 20 minutach zatrzymałem się na 50 000 jednostek). To nieliniowe zachowanie nie jest tak znaczące we wszystkich innych testach.
źródło
AutoDetectChangesEnabled = false;
w DbContext. Ma także duży dodatkowy efekt wydajnościowy: stackoverflow.com/questions/5943394/...DbContext
NIEObjectContext
?Ta kombinacja wystarczająco dobrze zwiększa prędkość.
źródło
Najszybszym sposobem byłoby użycie rozszerzenia wkładki luzem , które opracowałem
Uwaga: jest to produkt komercyjny, nie bezpłatnie
Wykorzystuje SqlBulkCopy i niestandardowy moduł danych, aby uzyskać maksymalną wydajność. W rezultacie jest ponad 20 razy szybszy niż zwykłe wstawianie lub AddRange
użycie jest niezwykle proste
źródło
Powinieneś spojrzeć na użycie
System.Data.SqlClient.SqlBulkCopy
do tego. Oto dokumentacja i oczywiście jest wiele samouczków online.Przykro mi, wiem, że szukałeś prostej odpowiedzi, aby EF mógł zrobić to, co chcesz, ale operacje masowe nie są tak naprawdę przeznaczone do ORM.
źródło
Zgadzam się z Adamem Rackisem.
SqlBulkCopy
to najszybszy sposób przesyłania zbiorczych rekordów z jednego źródła danych do drugiego. Użyłem tego do skopiowania rekordów 20 000 i zajęło to mniej niż 3 sekundy. Spójrz na poniższy przykład.źródło
AsDataReader()
metoda rozszerzenia, wyjaśniona w tej odpowiedzi: stackoverflow.com/a/36817205/1507899Poleciłbym ten artykuł, w jaki sposób robić wkładki zbiorcze za pomocą EF.
Entity Framework i powolne WSTAWKI luzem
Bada te obszary i porównuje występy:
źródło
jak nigdy tu nie wspomniano, chcę polecić EFCore.BulkExtensions tutaj
źródło
Zbadałem odpowiedź Slaumy (co jest niesamowite, dzięki za pomysłowca) i zmniejszałem wielkość partii, aż osiągną optymalną prędkość. Patrząc na wyniki Slauma:
Widoczne jest zwiększenie prędkości przy przechodzeniu od 1 do 10 i od 10 do 100, ale ze 100 do 1000 prędkość wstawiania ponownie spada.
Więc skupiłem się na tym, co się dzieje, gdy zmniejszasz wielkość partii do wartości między 10 a 100, a oto moje wyniki (używam innej zawartości wiersza, więc moje czasy mają inną wartość):
W oparciu o moje wyniki, rzeczywiste optimum wynosi około 30 dla wielkości partii. Jest mniej niż 10 i 100. Problem w tym, że nie mam pojęcia, dlaczego 30 jest optymalne, ani nie mogłem znaleźć żadnego logicznego wyjaśnienia tego.
źródło
Jak powiedzieli inni, SqlBulkCopy to sposób na zrobienie tego, jeśli chcesz naprawdę dobrej wydajności wstawiania.
Jest to trochę kłopotliwe we wdrożeniu, ale istnieją biblioteki, które mogą ci w tym pomóc. Jest ich kilka, ale tym razem bezwstydnie podłączę własną bibliotekę: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
Jedyny potrzebny kod to:
Ile to jest szybsze? Bardzo trudno powiedzieć, ponieważ zależy to od wielu czynników, wydajności komputera, sieci, wielkości obiektu itp. Testy wydajności, które przeprowadziłem, sugerują, że 25 000 jednostek można wstawić około 10s standardowego sposobu na localhost JEŻELI zoptymalizujesz konfigurację EF, np. wspomniane w innych odpowiedziach. Z EFUtilities, które zajmuje około 300ms. Jeszcze bardziej interesujące jest to, że dzięki tej metodzie zaoszczędziłem około 3 milionów jednostek w ciągu 15 sekund, średnio około 200 000 jednostek na sekundę.
Jedynym problemem jest oczywiście konieczność wstawienia powiązanych danych. Można to zrobić skutecznie na serwerze SQL za pomocą powyższej metody, ale wymaga strategii generowania Id, która pozwala generować identyfikatory w kodzie aplikacji dla nadrzędnego, dzięki czemu można ustawić klucze obce. Można to zrobić za pomocą identyfikatorów GUID lub czegoś takiego jak generowanie identyfikatora HiLo.
źródło
EFBatchOperation
miał konstruktora, do którego przekazujesz,DbContext
zamiast przechodzenia do każdej metody statycznej. Generyczne wersjeInsertAll
iUpdateAll
automatycznie znaleźć kolekcję, podobnie jakDbContext.Set<T>
byłoby dobre.Dispose()
Kontekst stwarza problemy, jeśli elementyAdd()
polegają na innych wstępnie załadowanych elementach (np. właściwościach nawigacji) w kontekścieUżywam podobnej koncepcji, aby mój kontekst był mały, aby osiągnąć taką samą wydajność
Ale zamiast
Dispose()
kontekstu i odtworzyć, po prostu odłączam jednostki, które jużSaveChanges()
owiń go za pomocą try catch, a
TrasactionScope()
jeśli nie potrzebujesz, nie pokazuj go tutaj w celu utrzymania kodu w czystościźródło
Wiem, że to bardzo stare pytanie, ale jeden facet powiedział, że opracował metodę rozszerzenia do użycia wstawiania zbiorczego z EF, a kiedy sprawdziłem, odkryłem, że biblioteka kosztuje dziś 599 USD (dla jednego programisty). Może ma to sens dla całej biblioteki, jednak dla samej wstawki zbiorczej jest to zbyt wiele.
Oto bardzo prosta metoda rozszerzenia, którą stworzyłem. Najpierw używam tego w parze z bazą danych (najpierw nie testuj z kodem, ale myślę, że to działa tak samo). Zmień
YourEntities
z nazwą swojego kontekstu:Możesz użyć tego przeciwko dowolnej kolekcji, która dziedziczy
IEnumerable
, w następujący sposób:źródło
await bulkCopy.WriteToServerAsync(table);
Spróbuj użyć procedury składowanej , która otrzyma XML danych, które chcesz wstawić.
źródło
Zrobiłem ogólne rozszerzenie powyższego przykładu @Slauma;
Stosowanie:
źródło
Dostępne są biblioteki innych firm obsługujące wstawianie luzem:
Zobacz: Biblioteka Entity Framework Bulk Insert
Zachowaj ostrożność przy wyborze biblioteki wstawiania zbiorczego. Tylko rozszerzenia Entity Framework obsługują wszelkiego rodzaju powiązania i dziedziczenia i jest to jedyne obsługiwane.
Oświadczenie : Jestem właścicielem rozszerzeń Entity Framework
Ta biblioteka pozwala na wykonywanie wszystkich operacji masowych potrzebnych w Twoich scenariuszach:
Przykład
źródło
Użyj
SqlBulkCopy
:źródło
Jednym z najszybszych sposobów zapisania listy jest zastosowanie następującego kodu
AutoDetectChangesEnabled = false
Add, AddRange i SaveChanges: Nie wykrywa zmian.
ValidateOnSaveEnabled = false;
Nie wykrywa modułu do śledzenia zmian
Musisz dodać nuget
Teraz możesz użyć następującego kodu
źródło
SqlBulkCopy jest bardzo szybki
To jest moja realizacja:
źródło
[Aktualizacja 2019] EF Core 3.1
Zgodnie z tym, co powiedziano powyżej, wyłączenie AutoDetectChangesEnabled w EF Core działało idealnie: czas wstawienia został podzielony przez 100 (od wielu minut do kilku sekund, 10 000 rekordów z relacjami między tabelami)
Zaktualizowany kod to:
źródło
Oto porównanie wydajności między użyciem Entity Framework a użyciem klasy SqlBulkCopy na realistycznym przykładzie: Jak luzem wstawiać złożone obiekty do bazy danych SQL Server
Jak już podkreślili inni, ORM nie są przeznaczone do użycia w operacjach masowych. Oferują elastyczność, rozdzielenie problemów i inne korzyści, ale operacje masowe (z wyjątkiem masowego odczytu) nie są jedną z nich.
źródło
Inną opcją jest użycie SqlBulkTools dostępnych w Nuget. Jest bardzo łatwy w użyciu i ma kilka zaawansowanych funkcji.
Przykład:
Zobacz dokumentację, aby uzyskać więcej przykładów i zaawansowanych zastosowań. Oświadczenie: Jestem autorem tej biblioteki i wszelkie poglądy są moim zdaniem.
źródło
Zgodnie z moją wiedzą jest
no BulkInsert
wEntityFramework
celu zwiększenia wydajności ogromnych wkładek.W tym scenariuszu możesz przejść do SqlBulkCopy w,
ADO.net
aby rozwiązać problemźródło
WriteToServer
które zajmujeDataTable
.Czy próbowałeś kiedyś wstawić pracownika lub zadanie w tle?
W moim przypadku im wstawiam 7760 rejestrów, rozproszonych w 182 różnych tabelach z relacjami klucza obcego (według NavigationProperties).
Bez zadania zajęło to 2 i pół minuty. W ramach zadania (
Task.Factory.StartNew(...)
) zajęło to 15 sekund.Robię tylko
SaveChanges()
po dodaniu wszystkich bytów do kontekstu. (w celu zapewnienia integralności danych)źródło
Wszystkie opisane tutaj rozwiązania nie pomagają, ponieważ kiedy wykonujesz SaveChanges (), instrukcje insert są wysyłane do bazy danych jedna po drugiej, tak działa Entity.
A jeśli na przykład podróż do bazy danych iz powrotem trwa 50 ms, czas potrzebny na wstawienie to liczba rekordów x 50 ms.
Musisz użyć BulkInsert, oto link: https://efbulkinsert.codeplex.com/
Dzięki temu czas wstawiania został skrócony z 5-6 minut do 10-12 sekund.
źródło
Możesz użyć biblioteki pakietów Bulk . Wersja Bulk Insert 1.0.0 jest używana w projektach o strukturze Entity> = 6.0.0.
Więcej opisu można znaleźć tutaj - kod źródłowy operacji masowej
źródło
[NOWE ROZWIĄZANIE DLA POSTGRESQL] Hej, wiem, że to dość stary post, ale ostatnio miałem podobny problem, ale korzystaliśmy z Postgresql. Chciałem zastosować skuteczny bulkinsert, co okazało się dość trudne. Nie znalazłem żadnej odpowiedniej darmowej biblioteki, aby to zrobić na tym DB. Znalazłem tylko tego pomocnika: https://bytefish.de/blog/postgresql_bulk_insert/, który jest również dostępny w Nuget. Napisałem małego mapera, który automatycznie mapuje właściwości w sposób Entity Framework:
Używam go w następujący sposób (miałem podmiot o nazwie Zobowiązanie):
Pokazałem przykład transakcji, ale można to również zrobić przy normalnym połączeniu pobranym z kontekstu. enterpriseToAdd jest wyliczalny dla normalnych rekordów encji, które chcę zbiorczo wstawić do DB.
To rozwiązanie, do którego mam po kilku godzinach badań i prób, jest, jak można się spodziewać, znacznie szybsze i wreszcie łatwe w użyciu i darmowe! Naprawdę radzę korzystać z tego rozwiązania, nie tylko z wyżej wymienionych powodów, ale także dlatego, że jest to jedyne, z którym nie miałem problemów z samym Postgresqlem, wiele innych rozwiązań działa bezbłędnie, na przykład z SqlServer.
źródło
Sekret polega na wstawieniu do identycznej pustej tabeli pomostowej. Wkładki błyskawicznie się rozjaśniają. Następnie uruchom jedną wstawkę z tego do głównego dużego stołu. Następnie obetnij stół pomostowy gotowy do następnej partii.
to znaczy.
źródło
Ale dla więcej niż (+4000) wkładek zalecam stosowanie procedury składowanej. dołączony czas, który upłynął. Wstawiłem 11,788 wierszy w 20 "
to jest kod
źródło
Użyj procedury składowanej, która pobiera dane wejściowe w postaci xml.
Z kodu c # pass wstaw dane jako xml.
np. w c #, składnia wyglądałaby tak:
źródło
Użyj tej techniki, aby zwiększyć szybkość wstawiania rekordów w Entity Framework. Tutaj używam prostej procedury składowanej, aby wstawić rekordy. I do wykonania tej procedury składowanej używam metody .FromSql () Entity Framework, która wykonuje Raw SQL.
Kod procedury składowanej:
Następnie przejrzyj wszystkie 4000 rekordów i dodaj kod Entity Framework, który wykonuje zapisany zapis
procedura wykonuje się co 100 pętlę.
W tym celu tworzę zapytanie łańcuchowe w celu wykonania tej procedury, ciągle dołączam do niego każdy zestaw rekordów.
Następnie sprawdź, czy pętla działa w wielokrotnościach 100 i w takim przypadku uruchom ją za pomocą
.FromSql()
.Sprawdź poniższy kod:
źródło