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)?
quelle
Antworten:
Nachdem ich viel aus den Haaren gezogen hatte, stellte ich fest, dass die
foreach
Schlaufen die Schuldigen waren. Was passieren muss, ist, EF aufzurufen, es aber in einen Zieltyp zurückzugeben undIList<T>
dann eine Schleife auf demIList<T>
.Beispiel:
quelle
SaveChanges
während Sie noch Ergebnisse aus der Datenbank abrufen . Daher besteht eine andere Lösung darin, Änderungen zu speichern, sobald die Schleife abgeschlossen ist.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()
oderToArray()
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.
Mit den oben genannten Erweiterungsmethoden können Sie Ihre Abfrage folgendermaßen schreiben:
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: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.
Bei nachfolgenden Aufrufen müssen vorherige Ergebnisblöcke übersprungen werden. Daher wird die Verwendung von
row_number
:quelle
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:
quelle
In der Tat können Sie
foreach
mit 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.
quelle
Setzen Sie einfach
context.SaveChanges()
nach dem Ende Ihrerforeach
(Schleife).quelle
Verwenden Sie Ihre Auswahl immer als Liste
Z.B:
Durchlaufen Sie dann die Sammlung, während Sie die Änderungen speichern
quelle
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 ;-)
quelle
Machen Sie Ihre abfragbaren Listen zu .ToList () und es sollte gut funktionieren.
quelle
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.
quelle
SELECT
Anweisung in einer gespeicherten Prozedur, die eine leere Ergebnismenge erzeugte, und wenn diese Ergebnismenge nicht gelesen wurde, wurdeSaveChanges
diese Ausnahme ausgelöst.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:
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:
quelle
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
quelle
Wenn ich im Projekt genau das gleiche Problem hatte, lag das Problem nicht in der
foreach
oder.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:
Zu:
quelle
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.
quelle
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
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:
FromToRange ist eine einfache Struktur mit From- und To-Eigenschaften.
quelle
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.
quelle
Der folgende Code funktioniert für mich:
quelle
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:
quelle
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.
Mit dieser
EnsureTransactionsForFunctionsAndCommands
Option kann der SP ausgeführt werden, ohne eine eigene Transaktion zu erstellen, und der Fehler wird nicht mehr ausgelöst.DbContextConfiguration.EnsureTransactionsForFunctionsAndCommands-Eigenschaft
quelle
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:
quelle
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.
Ich hoffe, dies hilft bei der Lösung zukünftiger Probleme.
quelle