SqlException from Entity Framework - Nowa transakcja jest niedozwolona, ​​ponieważ w sesji działają inne wątki

600

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)?

Keith Barrows
źródło
6
To jest najbardziej szczegółowe pytanie, jakie kiedykolwiek widziałem.
9
Czy ktoś jeszcze tęskni za procedurami przechowywanymi?
David

Odpowiedzi:

690

Po długim wyciągnięciu włosów odkryłem, że foreachsprawcami były pętle. To, co musi się zdarzyć, to wywołać EF, ale zwrócić go do IList<T>tego typu docelowego, a następnie zapętlić IList<T>.

Przykład:

IList<Client> 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;
    // ...
}
Keith Barrows
źródło
14
Tak, to też sprawiło mi ból głowy. Prawie spadłem z krzesła, gdy znalazłem problem! Rozumiem techniczne przyczyny tego problemu, ale nie jest to intuicyjne i nie pomaga deweloperowi wpaść w „otchłań sukcesu” blogs.msdn.com/brada/archive/2003/10/02/50420. aspx
Doktor Jones
9
Czy to nie jest złe dla wydajności dużych zestawów danych? Jeśli masz miliony rekordów w tabeli. ToList () wessie je wszystkie do pamięci. Napotykam na ten bardzo problem i zastanawiałem się, czy możliwe są następujące działania: a) Odłącz encję b) Utwórz nowy obiekt ObjectContext i dołącz do niego odłączoną encję. c) Wywołaj SaveChanges () na nowym ObjectContext d) Odłącz encję od nowego ObjectContext e) Załącz ją z powrotem do starego ObjectContext
Abhijeet Patel
149
Problem polega na tym, że nie możesz zadzwonić, SaveChangesgdy nadal pobierasz wyniki z bazy danych. Dlatego innym rozwiązaniem jest zapisanie zmian po zakończeniu pętli.
Drew Noakes,
4
Po ugryzieniu dodałem to do Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/... Zapraszam do głosowania.
Ian Mercer
36
Nasi deweloperzy zwykle dołączają .ToList () do dowolnego zapytania LINQ bez zastanowienia się nad konsekwencjami. To musi być pierwszy raz .ToList () jest naprawdę przydatny!
Marc
267

Jak już zidentyfikowałeś, nie możesz zapisywać z poziomu, foreachktóry wciąż pobiera dane z bazy danych za pośrednictwem aktywnego czytnika.

Wywołanie ToList()lub ToArray()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.

public static class EntityFrameworkUtil
{
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
    {
        return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
    }

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
    {
        int chunkNumber = 0;
        while (true)
        {
            var query = (chunkNumber == 0)
                ? queryable 
                : queryable.Skip(chunkNumber * chunkSize);
            var chunk = query.Take(chunkSize).ToArray();
            if (chunk.Length == 0)
                yield break;
            yield return chunk;
            chunkNumber++;
        }
    }
}

Biorąc pod uwagę powyższe metody rozszerzenia, możesz napisać zapytanie w następujący sposób:

foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
    // do stuff
    context.SaveChanges();
}

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:

foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
    foreach (var client in chunk)
    {
        // do stuff
    }
    context.SaveChanges();
}

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.

SELECT TOP (100)                     -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

Kolejne połączenia muszą pomijać poprzednie fragmenty wyników, dlatego wprowadza użycie row_number:

SELECT TOP (100)                     -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
    SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
    OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
    FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100   -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC
Drew Noakes
źródło
17
Dzięki. Twoje wyjaśnienie było o wiele bardziej przydatne niż to oznaczone jako „Odebrano”.
Wagner da Silva,
1
To jest świetne. tylko jedna rzecz: jeśli pytasz o kolumnę i aktualizujesz wartość tej kolumny, musisz być chunkNumber ++; . Załóżmy, że masz kolumnę „ModifiedDate” i wykonujesz kwerendę. Gdzie (x => x.ModifiedDate! = Null), a na końcu foreach ustawiłeś wartość ModifiedDate. W ten sposób nie iterujesz połowy rekordów, ponieważ połowa rekordów jest pomijana.
Arvand,
Niestety na ogromnych zestawach danych masz OutofMemoryException - patrz objaśnienie w dużym zestawie danych Entity Framework, z wyjątkiem pamięci . Opisałem, jak odnowić kontekst każdej partii w SqlException z Entity Framework - Nowa transakcja jest niedozwolona, ​​ponieważ w sesji działają inne wątki
Michael Freidgeim
Myślę, że to powinno zadziałać. var skip = 0; const int take = 100; Lista empsoy; emps; while ((emps = db.Employees.Skip (skip) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Zrób rzeczy tutaj}} Sformułuję to odpowiedź, ale zostanie pochowany poniżej stosów odpowiedzi poniżej i odnosi się do tego pytania.
jwize
123

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:

// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
    foreach (var person in context.People)
    {
        // Change to person
    }
    context.SaveChanges();
}

// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
    using (var context = new MyContext())
    {
        foreach (var person in context.People)
        {
            // Change to person
            context.SaveChanges();
        }
    }
    transaction.Complete();
}

// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
    var people = context.People.ToList(); // Note that this forces the database
                                          // to evaluate the query immediately
                                          // and could be very bad for large tables.

    foreach (var person in people)
    {
        // Change to person
        context.SaveChanges();
    }
} 
Mark Stafford - MSFT
źródło
6
Jeśli wybierzesz trasę transakcji, samo wprowadzenie TransactionScope może go nie naprawić - nie zapomnij przedłużyć limitu czasu, jeśli to, co robisz, może zająć dużo czasu - na przykład, jeśli będziesz interaktywnie debugował kod Wywołanie DB. Oto kod wydłużający limit czasu transakcji do godziny: using (var transaction = new TransactionScope (TransactionScopeOption.Required, new TimeSpan (1, 0, 0)))
Chris Moschini,
Natknąłem się na ten błąd przy pierwszym przejściu z „ścieżki samouczka” na własny przykład! Dla mnie jednak prostsze rozwiązanie, ZAPISZ PO ITERACJI, tym lepiej! (Myślę, że tak jest w 99% przypadków, a tylko 1% MUSI wykonać bazę danych z zachowaniem WEWNĘTRZNEJ pętli)
spiderman
Obrzydliwy. Właśnie wpadłem na ten błąd. Bardzo paskudny. Druga sugestia działała dla mnie jak urok, wraz z przeniesieniem moich zmian Save do pętli. Myślałem, że zapisywanie zmian poza pętlą byłoby lepsze do grupowania zmian. Ale dobrze. Nie sądzę?! :(
Mr. Young,
Nie działało dla mnie .NET 4.5. Podczas korzystania z TransactionScope otrzymałem następujący błąd: „Główny dostawca nie powiódł się na EnlistTransaction. {” Menedżer transakcji partnera wyłączył obsługę transakcji zdalnych / sieciowych. (Wyjątek od HRESULT: 0x8004D025) „}”. W końcu wykonuję pracę poza iteracją.
Diganta Kumar
Korzystanie z TransactionScope jest niebezpieczne, ponieważ tabela jest zablokowana na czas całej transakcji.
Michael Freidgeim
19

Rzeczywiście nie można zapisać zmian w foreachpę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.

Edgardo Pichardo C.
źródło
2
Pomyślałem, że to interesujące zobaczyć „regularny system bazy danych (RDMS)” tutaj
Dinerdo
1
Wydaje się to niewłaściwe, ponieważ wielokrotne wywoływanie SaveChanges jest w porządku w 90% kontekstów w EF.
Pxtl
Wygląda na to, że wielokrotne wywoływanie SaveChanges jest w porządku, chyba że pętla foreach wykonuje iterację nad jednostką db.
kerbasaurus
1
Aha! Wprowadź kontekst do każdej pętli! (pffft ... o czym myślałem? ..) Dzięki!
Adam Cox,
18

Po prostu umieść context.SaveChanges()po zakończeniu swojej foreach(pętli).

Majid
źródło
To jest lepsza opcja, którą znalazłem w moim przypadku ze względu na oszczędzanie wewnątrz foreach
Almeida
2
Nie zawsze jest to opcja.
Pxtl
9

Zawsze używaj swojego wyboru jako Listy

Na przykład:

var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();

Następnie zapętlaj kolekcję podczas zapisywania zmian

 foreach (var item in tempGroupOfFiles)
             {
                 var itemToUpdate = item;
                 if (itemToUpdate != null)
                 {
                     itemToUpdate.FileStatusID = 8;
                     itemToUpdate.LastModifiedDate = DateTime.Now;
                 }
                 Entities.SaveChanges();

             }
mzonerz
źródło
1
To wcale nie jest dobra praktyka. Nie powinieneś wykonywać SaveChanges tak często, jeśli nie potrzebujesz, i zdecydowanie nie powinieneś „Zawsze używaj swojego wyboru jako Listy”
Dinerdo
@Dinerdo to naprawdę zależy od scenariusza. W moim przypadku mam 2 pętle foreach. Zewnętrzna miała zapytanie db jako listę. Na przykład ten foreach przechodzi przez urządzenia sprzętowe. Wewnętrzny foreach pobiera kilka danych z każdego urządzenia. Zgodnie z wymaganiami muszę zapisać do bazy danych dane po ich pobraniu z każdego urządzenia jeden po drugim. Nie jest możliwe zapisanie wszystkich danych na końcu procesu. Napotkałem ten sam błąd, ale rozwiązanie mzonerza zadziałało.
jstuardo
@jstuardo Nawet z partiami?
Dinerdo
@Dinerdo Zgadzam się, że nie jest to dobra praktyka na poziomie filozoficznym. Istnieje jednak kilka sytuacji, w których wewnątrz pętli for kod wywołuje inną metodę (powiedzmy metodę AddToLog ()), która zawiera lokalne wywołanie db.SaveChanges (). W tej sytuacji nie można tak naprawdę kontrolować połączenia z db.Save Changes. W takim przypadku użycie ToList () lub podobnej struktury będzie działać zgodnie z sugestią mzonerz. Dzięki!
A. Varma,
W praktyce zrani cię to bardziej niż pomoże. Trzymam się tego, co powiedziałem - ToList () zdecydowanie nie powinien być używany przez cały czas, a zapisywanie zmian po każdym pojedynczym elemencie jest czymś, czego należy unikać w miarę możliwości w aplikacji o wysokiej wydajności. To byłaby tymczasowa poprawka IMO. Niezależnie od tego, jaką metodę rejestrowania posiadasz, najlepiej też skorzystaj z buforowania.
Dinerdo
8

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 ;-)

Herman Van Der Blom
źródło
1
Dobrym sposobem na uniknięcie tego jest otwarcie czytnika w celu otwarcia drugiego i umieszczenie tych operacji w drugim czytniku. Jest to coś, czego możesz potrzebować podczas aktualizacji wzorca / szczegółów w strukturze encji. Otwierasz pierwsze połączenie dla rekordu głównego, a drugie dla rekordów szczegółowych. jeśli tylko czytasz, nie powinno być problemów. problemy występują podczas aktualizacji.
Herman Van Der Blom
Pomocne wyjaśnienie. masz rację, dobrze jest zrozumieć, dlaczego coś się dzieje.
Dov Miller
8

Tworzenie list z zapytaniami do .ToList () i powinno działać dobrze.

Wojciech Seweryn
źródło
1
Podaj przykład zamiast po prostu opublikować rozwiązanie.
Ronnie Oosting
5

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.

MikeKulls
źródło
3
Miałem ostatnio podobny problem: powodem w moim przypadku było SELECTstwierdzenie w procedurze składowanej, które dało pusty zestaw wyników, a jeśli ten zestaw wyników nie został odczytany, SaveChangeszwrócił ten wyjątek.
n0rd
To samo z nieprzeczytanym wynikiem SP, wielkie dzięki za podpowiedź)
Pavel K
4

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:

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

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:

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}
jjspierx
źródło
Jest to świetna alternatywa, o której pomyślałem i zrobiłem, ale należy ją pochwalić. Uwaga: i) można iterować jako wyliczenie, co jest dobre dla bardzo dużych zbiorów; ii) Możesz użyć polecenia NoTracking, aby uniknąć problemów z ładowaniem tylu rekordów (jeśli taki jest twój scenariusz); iii) Bardzo podoba mi się również opcja tylko klucza podstawowego - to bardzo sprytne, ponieważ ładujesz dużo mniej danych do pamięci, ale nie masz do czynienia z Take / Skip w potencjalnie dynamicznym bazowym zbiorze danych.
Todd
4

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

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }
Hemant Sakta
źródło
2

Tak więc w projekcie miałem dokładnie ten sam problem, którego problem nie dotyczył foreachlub .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:

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

Do:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();
VeldMuijz
źródło
Czy mógłbyś wyjaśnić, co według ciebie było problemem? rozwiązałeś to, tworząc za każdym razem nowy kontekst Db?
eran otzap
2

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.

nadir
źródło
2

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

EF tworzy drugą kopię danych, która wykorzystuje do wykrywania zmian (aby mogła zachować zmiany w bazie danych). EF przechowuje ten drugi zestaw przez cały okres istnienia kontekstu, a ten zestaw wyczerpuje pamięć.

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:

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange to prosta struktura z właściwościami From i To.

Michael Freidgeim
źródło
Nie widziałem, jak „odnawiasz” swój kontekst. Wygląda na to, że po prostu tworzysz nowy kontekst dla każdej porcji.
Suncat2000
@ Suncat2000, masz rację, kontekst powinien być krótkotrwałym obiektem stackoverflow.com/questions/43474112/…
Michael Freidgeim
1

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.

Vinod T. Patil
źródło
7
Co oznacza „zamknij czytnik” w Entity Framework? W zapytaniu takim jak var result = od klienta nie ma widocznego czytnika w myDb.Customers, gdzie customer.Id == customerId wybierz klienta; zwraca wynik.FirstOrDefault ();
Anthony
@Anthony Jak mówią inne odpowiedzi, jeśli użyjesz EF do wyliczenia w zapytaniu LINQ (IQueryable), bazowy DataReader pozostanie otwarty aż do iteracji ostatniego wiersza. Ale chociaż MARS jest ważną funkcją, którą należy włączyć w ciągu połączenia, problem w OP nadal nie został rozwiązany przy pomocy samego MARS. Problem polega na próbie zapisania zmian, gdy bazowy czytnik danych jest nadal otwarty.
Todd
1

Poniższy kod działa dla mnie:

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}
użytkownik2918896
źródło
2
Witamy w SO! Rozważ dodanie wyjaśnienia i / lub linków opisujących, dlaczego to działa dla Ciebie. Odpowiedzi zawierające tylko kod są zwykle uważane za niezbyt dobrą jakość dla SO.
codeMagic
1

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:

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}
Tomas Kubes
źródło
1

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ę.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

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ść

JamPickle
źródło
0

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:

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    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 ### //
                    }
                }
Usman
źródło
0

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.

Więc w mojej sytuacji: Sprawdź zapytanie pod kątem błędów, debugując wynik, który zwraca zapytanie. Następnie popraw zapytanie.

Mam nadzieję, że pomoże to rozwiązać przyszłe problemy.

Max
źródło