SqlException from Entity Framework - Neue Transaktion ist nicht zulässig, da in der Sitzung andere Threads ausgeführt werden

600

Ich erhalte derzeit diesen Fehler:

System.Data.SqlClient.SqlException: Neue Transaktion ist nicht zulässig, da in der Sitzung andere Threads ausgeführt werden.

während Sie diesen Code ausführen:

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

Modell 1 - Dieses Modell befindet sich in einer Datenbank auf unserem Dev Server. Modell Nr. 1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Modell 2 - Dieses Modell befindet sich in einer Datenbank auf unserem Prod Server und wird täglich durch automatische Feeds aktualisiert. Alternativtext http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Hinweis - Die rot eingekreisten Elemente in Modell 1 sind die Felder, die ich zum "Zuordnen" zu Modell 2 verwende. Bitte ignorieren Sie die roten Kreise in Modell 2: Das ist eine andere Frage, die ich hatte und die jetzt beantwortet wird.

Hinweis: Ich muss noch eine isDeleted-Prüfung durchführen, damit ich sie sanft aus DB1 löschen kann, wenn sie aus dem Inventar unseres Kunden verschwunden ist.

Mit diesem speziellen Code möchte ich lediglich eine Firma in DB1 mit einem Client in DB2 verbinden, deren Produktliste aus DB2 abrufen und in DB1 einfügen, falls diese noch nicht vorhanden ist. Das erste Mal sollte eine vollständige Bestandsaufnahme erfolgen. Jedes Mal, wenn es dort ausgeführt wird, sollte nichts passieren, es sei denn, über Nacht wurde neues Inventar in den Feed aufgenommen.

Die große Frage ist also, wie ich den Transaktionsfehler lösen kann, den ich bekomme. Muss ich meinen Kontext jedes Mal durch die Schleifen löschen und neu erstellen (macht für mich keinen Sinn)?

Keith Barrows
quelle
6
Dies ist die detaillierteste Frage, die ich je gesehen habe.
9
Vermisst jemand schon gespeicherte Prozeduren?
David

Antworten:

690

Nachdem ich viel aus den Haaren gezogen hatte, stellte ich fest, dass die foreachSchlaufen die Schuldigen waren. Was passieren muss, ist, EF aufzurufen, es aber in einen Zieltyp zurückzugeben und IList<T>dann eine Schleife auf dem IList<T>.

Beispiel:

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
quelle
14
Ja, das hat mir auch Kopfschmerzen bereitet. Ich wäre fast von meinem Stuhl gefallen, als ich das Problem fand! Ich verstehe die technischen Gründe für das Problem, aber dies ist nicht intuitiv und hilft dem Entwickler nicht, in die "Grube des Erfolgs" blogs.msdn.com/brada/archive/2003/10/02/50420 zu fallen. Aspx
Doktor Jones
9
Ist das nicht schlecht für die Leistung großer Datenmengen? Wenn Sie eine Million Datensätze in der Tabelle haben. ToList () saugt sie alle in den Speicher. Ich bin auf genau dieses Problem gestoßen und habe mich gefragt, ob Folgendes möglich wäre: a) Trennen Sie die Entität b) Erstellen Sie einen neuen ObjectContext und hängen Sie die getrennte Entität daran an. c) Rufen Sie SaveChanges () für den neuen ObjectContext auf. d) Trennen Sie die Entität vom neuen ObjectContext. e) Hängen Sie sie um
Abhijeet Patel
149
Das Problem ist, dass Sie nicht anrufen können, SaveChangeswährend Sie noch Ergebnisse aus der Datenbank abrufen . Daher besteht eine andere Lösung darin, Änderungen zu speichern, sobald die Schleife abgeschlossen ist.
Drew Noakes
4
Nachdem ich auch gebissen wurde, fügte ich dies zu Microsoft Connect hinzu: connect.microsoft.com/VisualStudio/feedback/details/612369/… Sie können es gerne abstimmen.
Ian Mercer
36
Unsere Entwickler neigen dazu, .ToList () an jede LINQ-Abfrage anzuhängen, ohne über die Konsequenzen nachzudenken. Dies muss das erste Mal sein, dass .ToList () wirklich nützlich ist!
Marc
267

Wie Sie bereits identifiziert haben, können Sie nicht foreachüber einen aktiven Reader in einem Bereich speichern , der noch aus der Datenbank gezeichnet wird.

Das Aufrufen von ToList()oder ToArray()ist für kleine Datenmengen in Ordnung, aber wenn Sie Tausende von Zeilen haben, verbrauchen Sie viel Speicher.

Es ist besser, die Zeilen in Blöcken zu laden.

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++;
        }
    }
}

Mit den oben genannten Erweiterungsmethoden können Sie Ihre Abfrage folgendermaßen schreiben:

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

Das abfragbare Objekt, für das Sie diese Methode aufrufen, muss bestellt werden. Dies liegt daran, dass Entity Framework nur IQueryable<T>.Skip(int)geordnete Abfragen unterstützt. Dies ist sinnvoll, wenn Sie berücksichtigen, dass für mehrere Abfragen für verschiedene Bereiche die Reihenfolge stabil sein muss. Wenn die Reihenfolge für Sie nicht wichtig ist, bestellen Sie einfach nach Primärschlüssel, da dieser wahrscheinlich einen Clustered-Index hat.

Diese Version fragt die Datenbank in Stapeln von 100 ab. Beachten Sie, dass dies SaveChanges()für jede Entität aufgerufen wird.

Wenn Sie Ihren Durchsatz drastisch verbessern möchten, sollten Sie SaveChanges()weniger häufig anrufen . Verwenden Sie stattdessen folgenden Code:

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

Dies führt zu 100-mal weniger Datenbankaktualisierungsaufrufen. Natürlich dauert jeder dieser Anrufe länger, aber am Ende sind Sie immer noch weit vorne. Ihr Kilometerstand kann variieren, aber das war für mich Welten schneller.

Und es umgeht die Ausnahme, die Sie gesehen haben.

BEARBEITEN Ich habe diese Frage nach dem Ausführen von SQL Profiler erneut geprüft und einige Dinge aktualisiert, um die Leistung zu verbessern. Für alle Interessierten gibt es hier ein Beispiel für SQL, das zeigt, was von der Datenbank erstellt wird.

Die erste Schleife muss nichts überspringen, ist also einfacher.

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

Bei nachfolgenden Aufrufen müssen vorherige Ergebnisblöcke übersprungen werden. Daher wird die Verwendung von 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
quelle
17
Vielen Dank. Ihre Erklärung war viel nützlicher als die als "Beantwortet" gekennzeichnete.
Wagner da Silva
1
Das ist toll. Nur eine Sache: Wenn Sie eine Spalte abfragen und den Wert dieser Spalte aktualisieren, müssen Sie die Datei chunkNumber ++ verwenden. . Angenommen, Sie haben eine Spalte "ModifiedDate" und fragen ab .Where (x => x.ModifiedDate! = Null). Am Ende von foreach legen Sie einen Wert für ModifiedDate fest. Auf diese Weise iterieren Sie nicht die Hälfte der Datensätze, da die Hälfte der Datensätze übersprungen wird.
Arvand
Leider erhalten Sie bei großen Datenmengen eine OutofMemoryException - siehe Erklärung im großen Datensatz des Entity Frameworks, Ausnahme des Speichers . Ich habe beschrieben, wie Sie Ihren Kontext für jeden Stapel in SqlException von Entity Framework
Michael Freidgeim
Ich denke das sollte funktionieren. var skip = 0; const int take = 100; List <Employee> emps; while ((emps = db.Employees.Skip (überspringen) .Take (take) .ToList ()). Count> 0) {skip + = take; foreach (var emp in emps) {// Mach hier was}} Ich würde dies als Antwort formulieren, aber es würde unter den Stapel von Antworten unten vergraben sein und es bezieht sich auf diese Frage.
Jwize
123

Wir haben jetzt eine offizielle Antwort auf den auf Connect geöffneten Fehler veröffentlicht . Die von uns empfohlenen Problemumgehungen lauten wie folgt:

Dieser Fehler ist darauf zurückzuführen, dass Entity Framework während des Aufrufs von SaveChanges () eine implizite Transaktion erstellt. Der beste Weg, um den Fehler zu umgehen, besteht darin, ein anderes Muster zu verwenden (dh nicht zu speichern, während Sie gerade lesen) oder eine Transaktion explizit zu deklarieren. Hier sind drei mögliche Lösungen:

// 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
quelle
6
Wenn Sie die Transaktionsroute wählen, kann dies möglicherweise nicht behoben werden, wenn Sie nur ein TransactionScope einwerfen. Vergessen Sie nicht, das Timeout zu verlängern, wenn das, was Sie tun, lange dauern kann. Beispiel: Wenn Sie den Code, der das erstellt, interaktiv debuggen DB-Aufruf. Hier ist Code, der das Transaktionszeitlimit auf eine Stunde verlängert: using (var transaction = new TransactionScope (TransactionScopeOption.Required, new TimeSpan (1, 0, 0))
Chris Moschini
Ich bin auf diesen Fehler gestoßen, als ich zum ersten Mal vom "Tutorial-Pfad" in ein echtes Beispiel für mich selbst abgewichen bin! Für mich ist es jedoch umso besser, je einfacher die Lösung SAVE AFTER ITERATION ist! (Ich denke, 99% der Fälle ist dies der Fall, und nur 1% müssen wirklich eine Datenbank ausführen, um IN der Schleife zu speichern)
Spiderman
Brutto. Ich bin gerade auf diesen Fehler gestoßen. Sehr böse. Der zweite Vorschlag wirkte wie ein Zauber für mich und verschob meine SaveChanges in die Schleife. Ich dachte, Änderungen außerhalb der Schleife zu speichern wäre besser für das Stapeln von Änderungen. Aber in Ordnung. Ich denke nicht?! :(
Mr. Young
Hat bei mir nicht funktioniert .NET 4.5. Bei Verwendung des TransactionScope wurde die folgende Fehlermeldung angezeigt: "Der zugrunde liegende Anbieter ist bei EnlistTransaction fehlgeschlagen. {" Der Transaktionsmanager des Partners hat die Unterstützung für Remote- / Netzwerktransaktionen deaktiviert. (Ausnahme von HRESULT: 0x8004D025) "}". Am Ende mache ich den Job außerhalb der Iteration.
Diganta Kumar
Die Verwendung von TransactionScope ist gefährlich, da die Tabelle für die Zeit der gesamten Transaktion gesperrt ist.
Michael Freidgeim
19

In der Tat können Sie foreachmit Entity Framework keine Änderungen in einer Schleife in C # speichern .

context.SaveChanges() Die Methode verhält sich wie ein Commit auf einem regulären Datenbanksystem (RDMS).

Nehmen Sie einfach alle Änderungen vor (das Entity Framework wird zwischengespeichert) und speichern Sie alle auf einmal, indem Sie SaveChanges()nach der Schleife (außerhalb davon) aufrufen , wie bei einem Datenbank-Commit-Befehl.

Dies funktioniert, wenn Sie alle Änderungen gleichzeitig speichern können.

Edgardo Pichardo C.
quelle
2
Ich fand es interessant, "reguläres Datenbanksystem (RDMS)" hier zu sehen
Dinerdo
1
Dies scheint falsch zu sein, da das wiederholte Aufrufen von SaveChanges in 90% der Kontexte in EF in Ordnung ist.
Pxtl
Es scheint, als wäre es in Ordnung, SaveChanges wiederholt aufzurufen, es sei denn, die foreach-Schleife iteriert über eine Datenbankentität.
Kerbasaurus
1
Aha! Bringen Sie den Kontext für jede Schleife ein! (pffft ... was habe ich gedacht? ..) Danke!
Adam Cox
18

Setzen Sie einfach context.SaveChanges()nach dem Ende Ihrer foreach(Schleife).

Majid
quelle
Dies ist die bessere Option, die ich in meinem Fall herausgefunden habe, weil ich in foreach
Almeida
2
Dies ist nicht immer eine Option.
Pxtl
9

Verwenden Sie Ihre Auswahl immer als Liste

Z.B:

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

Durchlaufen Sie dann die Sammlung, während Sie die Änderungen speichern

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

             }
mzonerz
quelle
1
Dies ist überhaupt keine gute Praxis. Sie sollten SaveChanges nicht so oft ausführen, wenn Sie dies nicht benötigen, und Sie sollten definitiv nicht "Verwenden Sie Ihre Auswahl immer als Liste"
Dinerdo
@Dinerdo es kommt wirklich auf das Szenario an. In meinem Fall habe ich 2 foreach-Schleifen. Äußere hatte die Datenbankabfrage als Liste. Dies durchläuft beispielsweise jedes Hardwaregerät. Inner foreach ruft mehrere Daten von jedem Gerät ab. Je nach Anforderung muss ich die Daten in der Datenbank speichern, nachdem sie einzeln von jedem Gerät abgerufen wurden. Es ist nicht möglich, alle Daten am Ende des Prozesses zu speichern. Ich habe den gleichen Fehler festgestellt, aber die Lösung von mzonerz hat funktioniert.
Jstuardo
@jstuardo Auch mit Batching?
Dinerdo
@Dinerdo Ich stimme zu, dass es auf philosophischer Ebene keine gute Praxis ist. Es gibt jedoch mehrere Situationen, in denen der Code innerhalb der for-Schleife eine andere Methode aufruft (z. B. eine AddToLog () -Methode), die einen lokalen Aufruf von db.SaveChanges () enthält. In dieser Situation können Sie den Aufruf von db.Save Changes nicht wirklich steuern. In diesem Fall funktioniert die Verwendung einer ToList () oder einer ähnlichen Struktur wie von mzonerz vorgeschlagen. Vielen Dank!
A. Varma
In der Praxis wird dies Sie mehr verletzen als helfen. Ich stehe zu dem, was ich gesagt habe - ToList () sollte definitiv nicht immer verwendet werden, und das Speichern von Änderungen nach jedem einzelnen Element sollte in einer Hochleistungs-App nach Möglichkeit vermieden werden. Dies wäre ein temporärer Fix IMO. Unabhängig von der Protokollierungsmethode sollten Sie auch die Pufferung idealerweise nutzen.
Dinerdo
8

Zu Ihrer Information: Aus einem Buch und einigen Zeilen angepasst, weil es immer noch gültig ist:

Durch Aufrufen der SaveChanges () -Methode wird eine Transaktion gestartet, bei der automatisch alle Änderungen rückgängig gemacht werden, die in der Datenbank beibehalten wurden, wenn vor Abschluss der Iteration eine Ausnahme auftritt. Andernfalls wird die Transaktion festgeschrieben. Sie könnten versucht sein, die Methode nach jeder Aktualisierung oder Löschung von Entitäten anzuwenden, anstatt nach Abschluss der Iteration, insbesondere wenn Sie eine große Anzahl von Entitäten aktualisieren oder löschen.

Wenn Sie versuchen, SaveChanges () aufzurufen, bevor alle Daten verarbeitet wurden, tritt die Ausnahme "Neue Transaktion ist nicht zulässig, da in der Sitzung andere Threads ausgeführt werden" auf. Die Ausnahme tritt auf, weil SQL Server das Starten einer neuen Transaktion für eine Verbindung mit geöffnetem SqlDataReader nicht zulässt, selbst wenn mehrere aktive Datensatzgruppen (MARS) durch die Verbindungszeichenfolge aktiviert sind (die Standardverbindungszeichenfolge von EF aktiviert MARS).

Manchmal ist es besser zu verstehen, warum Dinge passieren ;-)

Herman Van Der Blom
quelle
1
Eine gute Möglichkeit, dies zu vermeiden, besteht darin, einen Reader zu öffnen, um einen zweiten zu öffnen und diese Vorgänge in den zweiten Reader einzufügen. Dies ist etwas, das Sie benötigen können, wenn Sie Master / Details im Entity Framework aktualisieren. Sie öffnen die erste Verbindung für den Stammsatz und die zweite für die Detaildatensätze. Wenn Sie nur lesen, sollte es keine Probleme geben. Die Probleme treten beim Aktualisieren auf.
Herman Van Der Blom
Hilfreiche Erklärung. Sie haben Recht, es ist gut zu verstehen, warum Dinge passieren.
Dov Miller
8

Machen Sie Ihre abfragbaren Listen zu .ToList () und es sollte gut funktionieren.

Wojciech Seweryn
quelle
1
Bitte geben Sie ein Beispiel an, anstatt nur eine Lösung zu veröffentlichen.
Ronnie Oosting
5

Ich bekam das gleiche Problem, aber in einer anderen Situation. Ich hatte eine Liste von Elementen in einem Listenfeld. Der Benutzer kann auf ein Element klicken und Löschen auswählen, aber ich verwende einen gespeicherten Prozess, um das Element zu löschen, da das Löschen des Elements viel Logik erfordert. Wenn ich den gespeicherten Prozess aufrufe, funktioniert das Löschen einwandfrei, aber jeder zukünftige Aufruf von SaveChanges verursacht den Fehler. Meine Lösung bestand darin, den gespeicherten Prozess außerhalb von EF aufzurufen, und dies funktionierte einwandfrei. Aus irgendeinem Grund lässt etwas offen, wenn ich den gespeicherten Prozess mit der EF-Methode aufrufe.

MikeKulls
quelle
3
Hatte kürzlich ein ähnliches Problem: Der Grund in meinem Fall war eine SELECTAnweisung in einer gespeicherten Prozedur, die eine leere Ergebnismenge erzeugte, und wenn diese Ergebnismenge nicht gelesen wurde, wurde SaveChangesdiese Ausnahme ausgelöst.
n0rd
Gleiches mit ungelesenem Ergebnis von SP, vielen Dank für den Hinweis)
Pavel K
4

Hier sind zwei weitere Optionen, mit denen Sie SaveChanges () in einer für jede Schleife aufrufen können.

Die erste Option besteht darin, einen DBContext zu verwenden, um Ihre Listenobjekte zum Durchlaufen zu generieren, und dann einen zweiten DBContext zum Aufrufen von SaveChanges () zu erstellen. Hier ist ein Beispiel:

//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()
    }
}

Die zweite Option besteht darin, eine Liste der Datenbankobjekte aus dem DBContext abzurufen, aber nur die IDs auszuwählen. Durchlaufen Sie dann die Liste der IDs (vermutlich ein Int), rufen Sie das Objekt ab, das jedem Int entspricht, und rufen Sie auf diese Weise SaveChanges () auf. Die Idee hinter dieser Methode ist das Abrufen einer großen Liste von Ganzzahlen, die viel effizienter ist als das Abrufen einer großen Liste von Datenbankobjekten und das Aufrufen von .ToList () für das gesamte Objekt. Hier ist ein Beispiel für diese Methode:

//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
quelle
Dies ist eine großartige Alternative, an die ich gedacht und getan habe, aber dies muss positiv bewertet werden. Hinweis: i) Sie können als Aufzähler iterieren, was für sehr große Mengen gut ist. ii) Sie können den Befehl NoTracking verwenden, um Probleme beim Laden so vieler Datensätze zu vermeiden (wenn dies Ihr Szenario ist). iii) Ich mag auch die Option "Nur Primärschlüssel" sehr - das ist sehr klug, weil Sie viel weniger Daten in den Speicher laden, sich aber nicht mit Take / Skip für ein potenziell dynamisches zugrunde liegendes Dataset befassen.
Todd
4

Wenn Sie diesen Fehler aufgrund von foreach erhalten und wirklich eine Entität zuerst in der Schleife speichern und die generierte Identität weiter in der Schleife verwenden müssen, wie in meinem Fall, besteht die einfachste Lösung darin, einen anderen DBContext zu verwenden, um eine Entität einzufügen, die die ID zurückgibt und verwendet diese ID im äußeren Kontext

Zum Beispiel

    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
quelle
2

Wenn ich im Projekt genau das gleiche Problem hatte, lag das Problem nicht in der foreachoder .toList()in der von uns verwendeten AutoFac-Konfiguration. Dies führte zu einigen seltsamen Situationen, in denen der obige Fehler ausgelöst wurde, aber auch eine Reihe anderer gleichwertiger Fehler.

Dies war unser Fix: Geändert:

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

Zu:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();
VeldMuijz
quelle
Könnten Sie näher erläutern, was Ihrer Meinung nach das Problem war? Sie haben dies gelöst, indem Sie jedes Mal einen neuen Dbcontext erstellt haben?
eran otzap
2

Ich weiß, dass es eine alte Frage ist, aber ich bin heute mit diesem Fehler konfrontiert.

und ich fand, dass dieser Fehler ausgelöst werden kann, wenn ein Datenbanktabellen-Trigger einen Fehler erhält.

Zu Ihrer Information können Sie auch Ihre Tabellenauslöser überprüfen, wenn Sie diesen Fehler erhalten.

Nadir
quelle
2

Ich musste ein riesiges ResultSet lesen und einige Datensätze in der Tabelle aktualisieren. Ich habe versucht, Brocken zu verwenden, wie in Drew Noakes ' Antwort vorgeschlagen .

Leider habe ich nach 50000 Datensätzen eine OutofMemoryException. Die Antwort Entity Framework große Datenmenge, nicht genügend Speicher Ausnahme erklärt, dass

EF erstellt eine zweite Kopie der Daten, die zur Änderungserkennung verwendet werden (damit Änderungen an der Datenbank beibehalten werden können). EF enthält diesen zweiten Satz für die Lebensdauer des Kontexts und diesen Satz, bei dem Ihnen der Speicher ausgeht.

Es wird empfohlen, den Kontext für jeden Stapel neu zu erstellen.

Also habe ich Minimal- und Maximalwerte des Primärschlüssels abgerufen - die Tabellen haben Primärschlüssel als automatisch inkrementelle Ganzzahlen. Dann habe ich Datensätze aus der Datenbank abgerufen, indem ich den Kontext für jeden Block geöffnet habe. Nach der Verarbeitung wird der Chunk-Kontext geschlossen und der Speicher freigegeben. Es stellt sicher, dass die Speichernutzung nicht wächst.

Unten ist ein Ausschnitt aus meinem Code:

  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 ist eine einfache Struktur mit From- und To-Eigenschaften.

Michael Freidgeim
quelle
Ich habe nicht gesehen, wie Sie Ihren Kontext "erneuert" haben. Es sieht so aus, als würden Sie einfach für jeden Block einen neuen Kontext erstellen.
Suncat2000
@ Suncat2000, Sie haben Recht, der Kontext sollte ein kurzlebiges Objekt sein. Stackoverflow.com/questions/43474112/…
Michael Freidgeim
1

Ich stand auch vor dem gleichen Problem.

Hier ist die Ursache und Lösung.

http://blogs.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transactions-and-sql-error-3997-3988-or-3983.aspx

Stellen Sie vor dem Auslösen von Datenmanipulationsbefehlen wie Einfügungen und Aktualisierungen sicher, dass Sie alle vorherigen aktiven SQL-Reader geschlossen haben.

Der häufigste Fehler sind Funktionen, die Daten aus der Datenbank lesen und Werte zurückgeben. Zum Beispiel Funktionen wie isRecordExist.

In diesem Fall kehren wir sofort von der Funktion zurück, wenn wir den Datensatz gefunden haben und vergessen, den Reader zu schließen.

Vinod T. Patil
quelle
7
Was bedeutet "Leser schließen" in Entity Framework? In einer Abfrage wie var result = from customer in myDb.Customers ist kein sichtbarer Leser vorhanden. Customer.Id == customerId select customer; Ergebnis zurückgeben.FirstOrDefault ();
Anthony
@Anthony Wie andere Antworten sagen, bleibt der zugrunde liegende DataReader geöffnet, wenn Sie EF zum Aufzählen einer LINQ-Abfrage (IQueryable) verwenden, bis die letzte Zeile durchlaufen wird. Obwohl MARS eine wichtige Funktion ist, die in einer Verbindungszeichenfolge aktiviert werden muss, wird das Problem im OP immer noch nicht mit MARS allein gelöst. Das Problem besteht darin, SaveChanges zu speichern, während ein zugrunde liegender DataReader noch geöffnet ist.
Todd
1

Der folgende Code funktioniert für mich:

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();
}
user2918896
quelle
2
Willkommen bei SO! Fügen Sie eine Erklärung und / oder Links hinzu, die beschreiben, warum dies für Sie funktioniert. Nur-Code-Antworten werden für SO normalerweise als nicht von guter Qualität angesehen.
CodeMagic
1

In meinem Fall trat das Problem auf, als ich Stored Procedure über EF aufrief und SaveChanges später diese Ausnahme auslöste. Das Problem bestand darin, die Prozedur aufzurufen, der Enumerator wurde nicht entsorgt. Ich habe den Code folgendermaßen korrigiert:

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
quelle
1

Nach der Migration von EF5 auf EF6 wurde der Fehler "Neue Transaktion ist nicht zulässig, da in der Sitzung andere Threads ausgeführt werden" angezeigt.

Google hat uns hierher gebracht, aber wir rufen nicht SaveChanges()innerhalb der Schleife an. Die Fehler wurden beim Ausführen einer gespeicherten Prozedur mit ObjectContext.ExecuteFunction in einer foreach-Schleife ausgelöst, die aus der Datenbank gelesen wurde.

Jeder Aufruf von ObjectContext.ExecuteFunction umschließt die Funktion in einer Transaktion. Das Starten einer Transaktion, während bereits ein Reader geöffnet ist, verursacht den Fehler.

Sie können das Umschließen des SP in eine Transaktion deaktivieren, indem Sie die folgende Option festlegen.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

Mit dieser EnsureTransactionsForFunctionsAndCommandsOption kann der SP ausgeführt werden, ohne eine eigene Transaktion zu erstellen, und der Fehler wird nicht mehr ausgelöst.

DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands-Eigenschaft

JamPickle
quelle
0

Ich bin viel zu spät zur Party, aber heute hatte ich den gleichen Fehler und wie ich es gelöst habe, war einfach. Mein Szenario ähnelte dem angegebenen Code, den ich für DB-Transaktionen in verschachtelten for-each-Schleifen erstellt habe.

Das Problem besteht darin, dass eine einzelne DB-Transaktion etwas länger dauert als für jede Schleife. Sobald die frühere Transaktion nicht abgeschlossen ist, löst die neue Traktion eine Ausnahme aus. Die Lösung besteht darin, ein neues Objekt in der for-each-Schleife zu erstellen wo Sie eine Datenbank-Transaktion durchführen.

Für die oben genannten Szenarien sieht die Lösung folgendermaßen aus:

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
quelle
0

Ich bin etwas spät dran, aber ich hatte auch diesen Fehler. Ich habe das Problem gelöst, indem ich überprüft habe, wo die Werte aktualisiert wurden.

Ich fand heraus, dass meine Abfrage falsch war und dass dort mehr als 250 Änderungen ausstanden. Also habe ich meine Abfrage korrigiert und jetzt funktioniert es richtig.

Also in meiner Situation: Überprüfen Sie die Abfrage auf Fehler, indem Sie über das Ergebnis debuggen, das die Abfrage zurückgibt. Danach korrigieren Sie die Abfrage.

Ich hoffe, dies hilft bei der Lösung zukünftiger Probleme.

Max
quelle