Obecnie otrzymuję ten błąd:
System.Data.SqlClient.SqlException: Nowa transakcja jest niedozwolona, ponieważ w sesji działają inne wątki.
podczas uruchamiania tego kodu:
public class ProductManager : IProductManager
{
#region Declare Models
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
#endregion
public IProduct GetProductById(Guid productId)
{
// Do a quick sync of the feeds...
SyncFeeds();
...
// get a product...
...
return product;
}
private void SyncFeeds()
{
bool found = false;
string feedSource = "AUTO";
switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
{
if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
{
var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
{
foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
{
if (targetProduct.alternateProductID == sourceProduct.AutoID)
{
found = true;
break;
}
}
if (!found)
{
var newProduct = new RivWorks.Model.Negotiation.Product();
newProduct.alternateProductID = sourceProduct.AutoID;
newProduct.isFromFeed = true;
newProduct.isDeleted = false;
newProduct.SKU = sourceProduct.StockNumber;
company.Product.Add(newProduct);
}
}
_dbRiv.SaveChanges(); // ### THIS BREAKS ### //
}
}
}
break;
}
}
}
Model nr 1 - ten model znajduje się w bazie danych na naszym serwerze deweloperskim. Model nr 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model nr 2 - ten model znajduje się w bazie danych na naszym serwerze Prod i jest aktualizowany codziennie przez automatyczne kanały. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
Uwaga - czerwone kółka w Modelu nr 1 to pola, których używam do „mapowania” do Modelu nr 2. Proszę zignorować czerwone kółka w Modelu 2: to pytanie pochodzi z innego pytania, na które mam teraz odpowiedź.
Uwaga: Nadal muszę wprowadzić czek isDeleted, aby móc go miękko usunąć z DB1, jeśli wyszedł z ekwipunku naszego klienta.
Wszystko, co chcę zrobić, z tym konkretnym kodem, to połączyć firmę w DB1 z klientem w DB2, pobrać listę produktów z DB2 i WSTAWIĆ ją w DB1, jeśli jeszcze jej nie ma. Za pierwszym razem powinno być pełne wyciągnięcie ekwipunku. Za każdym razem, gdy jest tam uruchamiany, nic nie powinno się zdarzyć, chyba że w ciągu nocy pojawił się nowy inwentarz na kanale.
Więc wielkie pytanie - jak rozwiązać błąd transakcji, który otrzymuję? Czy muszę za każdym razem upuszczać i odtwarzać mój kontekst za pomocą pętli (nie ma to dla mnie sensu)?
źródło
Odpowiedzi:
Po długim wyciągnięciu włosów odkryłem, że
foreach
sprawcami były pętle. To, co musi się zdarzyć, to wywołać EF, ale zwrócić go doIList<T>
tego typu docelowego, a następnie zapętlićIList<T>
.Przykład:
źródło
SaveChanges
gdy nadal pobierasz wyniki z bazy danych. Dlatego innym rozwiązaniem jest zapisanie zmian po zakończeniu pętli.Jak już zidentyfikowałeś, nie możesz zapisywać z poziomu,
foreach
który wciąż pobiera dane z bazy danych za pośrednictwem aktywnego czytnika.Wywołanie
ToList()
lubToArray()
jest w porządku w przypadku małych zestawów danych, ale gdy masz tysiące wierszy, zużyjesz dużą ilość pamięci.Lepiej jest ładować rzędy w kawałki.
Biorąc pod uwagę powyższe metody rozszerzenia, możesz napisać zapytanie w następujący sposób:
Obiekt, do którego można wywoływać tę metodę, musi zostać zamówiony. Wynika to z faktu, że Entity Framework obsługuje tylko
IQueryable<T>.Skip(int)
zapytania uporządkowane, co ma sens, gdy weźmie się pod uwagę, że wiele zapytań dla różnych zakresów wymaga stabilnego porządkowania. Jeśli kolejność nie jest dla Ciebie ważna, po prostu zamawiaj według klucza podstawowego, ponieważ może to mieć indeks klastrowany.Ta wersja wykona zapytanie do bazy danych w partiach po 100. Uwaga, która
SaveChanges()
jest wywoływana dla każdej jednostki.Jeśli chcesz znacznie zwiększyć przepustowość, powinieneś dzwonić
SaveChanges()
rzadziej. Zamiast tego użyj takiego kodu:Powoduje to 100 razy mniej wywołań aktualizacji bazy danych. Oczywiście, każde z tych połączeń zajmuje więcej czasu, ale w końcu wciąż wychodzisz daleko przed siebie. Twój przebieg może się różnić, ale dla mnie było to szybsze.
I omija wyjątek, który widziałeś.
EDYCJA Powtórzyłem to pytanie po uruchomieniu SQL Profiler i zaktualizowałem kilka rzeczy, aby poprawić wydajność. Dla każdego, kto jest zainteresowany, oto przykładowy SQL, który pokazuje, co jest tworzone przez DB.
Pierwsza pętla nie musi niczego pomijać, więc jest to prostsze.
Kolejne połączenia muszą pomijać poprzednie fragmenty wyników, dlatego wprowadza użycie
row_number
:źródło
Opublikowaliśmy teraz oficjalną odpowiedź na błąd otwarty w Connect . Obejścia, które zalecamy, są następujące:
Ten błąd jest spowodowany tworzeniem niejawnej transakcji przez Entity Framework podczas wywołania SaveChanges (). Najlepszym sposobem obejścia tego błędu jest użycie innego wzorca (tj. Nie zapisywanie w trakcie czytania) lub jawne zadeklarowanie transakcji. Oto trzy możliwe rozwiązania:
źródło
Rzeczywiście nie można zapisać zmian w
foreach
pętli w języku C # za pomocą Entity Framework.context.SaveChanges()
Metoda działa jak zatwierdzenie w zwykłym systemie baz danych (RDMS).Po prostu wprowadź wszystkie zmiany (które Entity Framework zbuforuje), a następnie zapisz je wszystkie naraz, wywołując
SaveChanges()
po pętli (poza nią), podobnie jak polecenie zatwierdzenia bazy danych.Działa to, jeśli możesz zapisać wszystkie zmiany naraz.
źródło
Po prostu umieść
context.SaveChanges()
po zakończeniu swojejforeach
(pętli).źródło
Zawsze używaj swojego wyboru jako Listy
Na przykład:
Następnie zapętlaj kolekcję podczas zapisywania zmian
źródło
Do Twojej wiadomości: z książki i niektórych poprawionych wierszy, ponieważ jej styl jest ważny:
Wywołanie metody SaveChanges () rozpoczyna transakcję, która automatycznie wycofuje wszystkie zmiany utrwalone w bazie danych, jeśli wystąpi wyjątek przed zakończeniem iteracji; w przeciwnym razie transakcja zostanie zatwierdzona. Możesz mieć ochotę zastosować tę metodę po każdej aktualizacji lub usunięciu encji, a nie po zakończeniu iteracji, zwłaszcza gdy aktualizujesz lub usuwasz ogromną liczbę encji.
Jeśli spróbujesz wywołać funkcję SaveChanges () przed przetworzeniem wszystkich danych, ponosisz wyjątek „Nowa transakcja jest niedozwolona, ponieważ w sesji działają inne wątki”. Wyjątek występuje, ponieważ SQL Server nie zezwala na rozpoczęcie nowej transakcji na połączeniu z otwartym SqlDataReader, nawet przy włączonym wielu zestawach rekordów aktywnych (MARS) przez ciąg połączenia (domyślny ciąg połączenia EF włącza MARS)
Czasami lepiej jest zrozumieć, dlaczego coś się dzieje ;-)
źródło
Tworzenie list z zapytaniami do .ToList () i powinno działać dobrze.
źródło
Dostawałem ten sam problem, ale w innej sytuacji. Miałem listę przedmiotów w polu listy. Użytkownik może kliknąć element i wybrać opcję Usuń, ale korzystam z zapisanego proc, aby usunąć element, ponieważ usuwanie elementu wymaga dużej logiki. Kiedy wywołam przechowywany proc, usuwanie działa dobrze, ale każde przyszłe wywołanie SaveChanges spowoduje błąd. Moim rozwiązaniem było wywołanie przechowywanego proc poza EF i to działało dobrze. Z jakiegoś powodu, gdy wywołuję przechowywany proc przy użyciu EF, robienia rzeczy, pozostawia coś otwartego.
źródło
SELECT
stwierdzenie w procedurze składowanej, które dało pusty zestaw wyników, a jeśli ten zestaw wyników nie został odczytany,SaveChanges
zwrócił ten wyjątek.Oto kolejne 2 opcje, które pozwalają na wywołanie SaveChanges () dla każdej pętli.
Pierwszą opcją jest użycie jednego DBContext do wygenerowania obiektów listy do iteracji, a następnie utworzenie drugiego DBContext do wywołania SaveChanges (). Oto przykład:
Drugą opcją jest pobranie listy obiektów bazy danych z DBContext, ale wybranie tylko identyfikatorów. A następnie iteruj przez listę identyfikatorów (prawdopodobnie int) i uzyskaj obiekt odpowiadający każdej int, i w ten sposób wywołaj SaveChanges (). Ideą tej metody jest chwytanie dużej liczby liczb całkowitych, jest o wiele bardziej wydajne niż uzyskiwanie dużej listy obiektów db i wywoływanie funkcji .ToList () na całym obiekcie. Oto przykład tej metody:
źródło
Jeśli pojawi się ten błąd z powodu foreach i naprawdę musisz najpierw zapisać jeden element w pętli i użyć wygenerowanej tożsamości w pętli, tak jak w moim przypadku, najłatwiejszym rozwiązaniem jest użycie innego kontekstu DBContext do wstawienia elementu, który zwróci identyfikator i użyje ten identyfikator w kontekście zewnętrznym
Na przykład
źródło
Tak więc w projekcie miałem dokładnie ten sam problem, którego problem nie dotyczył
foreach
lub.toList()
był w konfiguracji AutoFac, z której korzystaliśmy. Stworzyło to kilka dziwnych sytuacji, w których wyrzucono powyższy błąd, ale zgłoszono także wiele innych równoważnych błędów.To była nasza poprawka: Zmieniłem to:
Do:
źródło
Wiem, że to stare pytanie, ale dzisiaj napotkałem ten błąd.
i stwierdziłem, że ten błąd może zostać zgłoszony, gdy wyzwalacz tabeli bazy danych otrzyma błąd.
dla twojej informacji możesz również sprawdzić wyzwalacze tabel, gdy pojawi się ten błąd.
źródło
Musiałem przeczytać ogromny zestaw wyników i zaktualizować niektóre rekordy w tabeli. Próbuję użyć kawałki jak zasugerowano w Drew Noakes „s odpowiedź .
Niestety po 50000 rekordach mam OutofMemoryException. Odpowiedź wyjaśnia duży zestaw danych Entity Framework, z wyjątkiem braku pamięci
Zaleca się ponowne utworzenie kontekstu dla każdej partii.
Pobrałem więc minimalne i maksymalne wartości klucza podstawowego - tabele mają klucze podstawowe jako automatyczne przyrostowe liczby całkowite, a następnie pobrałem z bazy danych fragmenty rekordów, otwierając kontekst dla każdego fragmentu. Po przetworzeniu fragment fragmentu zamyka się i zwalnia pamięć. Zapewnia to, że użycie pamięci nie rośnie.
Poniżej znajduje się fragment mojego kodu:
FromToRange to prosta struktura z właściwościami From i To.
źródło
Miałem też do czynienia z tym samym problemem.
Oto przyczyna i rozwiązanie.
http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx
Upewnij się, że przed uruchomieniem poleceń manipulacji danymi, takich jak wstawki, aktualizacje, zamknąłeś wszystkie poprzednie aktywne czytniki SQL.
Najczęstszym błędem są funkcje, które odczytują dane z bazy danych i zwracają wartości. Na przykład funkcje takie jak isRecordExist.
W takim przypadku natychmiast wracamy z funkcji, jeśli znaleźliśmy rekord i zapomnieliśmy zamknąć czytnik.
źródło
Poniższy kod działa dla mnie:
źródło
W moim przypadku problem pojawił się, gdy zadzwoniłem do procedury składowanej za pośrednictwem EF, a następnie SaveChanges zgłosi ten wyjątek. Problem polegał na wywołaniu procedury, moduł wyliczający nie został usunięty. Naprawiłem kod w następujący sposób:
źródło
Po migracji z EF5 do EF6 zaczęliśmy widzieć błąd „Nowa transakcja jest niedozwolona, ponieważ w sesji działają inne wątki” .
Google nas tu przywiodło, ale nie dzwonimy
SaveChanges()
w pętli. Błędy zostały zgłoszone podczas wykonywania procedury składowanej za pomocą ObjectContext.ExecuteFunction wewnątrz odczytu pętli foreach z bazy danych.Każde wywołanie ObjectContext.ExecuteFunction zawija funkcję w transakcję. Rozpoczęcie transakcji, gdy jest już otwarty czytnik, powoduje błąd.
Możliwe jest wyłączenie zawijania SP w transakcji, ustawiając następującą opcję.
The
EnsureTransactionsForFunctionsAndCommands
opcja umożliwia uruchomienie SP bez tworzenia własnej transakcji, a błąd nie jest już zgłaszany.DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands Właściwość
źródło
Jestem spóźniony na imprezę, ale dzisiaj spotkałem się z tym samym błędem, a sposób, w jaki go rozwiązałem, był prosty. Mój scenariusz był podobny do podanego kodu, który dokonywałem transakcji DB w zagnieżdżonych pętlach dla każdego.
Problem polega na tym, że transakcja Single DB zajmuje trochę więcej czasu niż dla każdej pętli, więc gdy wcześniejsza transakcja nie zostanie zakończona, nowa trakcja zgłasza wyjątek, więc rozwiązaniem jest utworzenie nowego obiektu w pętli dla każdej z nich. gdzie dokonujesz transakcji db.
W przypadku wyżej wymienionych scenariuszy rozwiązanie będzie wyglądać następująco:
źródło
Jestem trochę spóźniony, ale miałem też ten błąd. Rozwiązałem problem, sprawdzając, gdzie są wartości, które były aktualizowane.
Dowiedziałem się, że moje zapytanie było niepoprawne i że tam czeka ponad 250 edycji. Więc poprawiłem swoje zapytanie, a teraz działa poprawnie.
Mam nadzieję, że pomoże to rozwiązać przyszłe problemy.
źródło