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




entity framework transaction isolation level (14)

Wenn Sie diesen Fehler wegen foreach bekommen und Sie müssen wirklich eine Entity zuerst innerhalb der Schleife speichern und die generierte Identität weiter in der Schleife verwenden, wie in meinem Fall, ist die einfachste Lösung, einen anderen DBContext zu verwenden, um die Id zurückzugeben und zu verwenden diese ID im äußeren Kontext

Beispielsweise

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }

Ich erhalte derzeit diesen Fehler:

System.Data.SqlClient.SqlException: Neue Transaktion ist nicht zulässig, da andere Threads in der Sitzung 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 Entwicklungsserver. Modell # 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 jeden Tag durch automatische Feeds aktualisiert. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Hinweis - Die rot eingekreisten Objekte in Modell # 1 sind die Felder, die ich verwende, um Modell Nr. 2 zuzuordnen. Bitte ignoriere die roten Kreise in Modell # 2: Das ist von einer anderen Frage, die ich hatte, die jetzt beantwortet wird.

Hinweis: Ich muss immer noch einen isDeleted-Check anlegen, damit ich ihn aus DB1 löschen kann, wenn er aus dem Inventar unseres Kunden verschwunden ist.

Alles, was ich mit diesem speziellen Code tun möchte, ist, eine Firma in DB1 mit einem Client in DB2 zu verbinden, ihre Produktliste von DB2 zu holen und sie in DB1 einzufügen, wenn sie nicht schon da ist. Das erste Mal sollte ein voller Inventar sein. Jedes Mal, wenn es dort ausgeführt wird, sollte nichts passieren, es sei denn, neues Inventar wurde über Nacht in das Feed aufgenommen.

Also die große Frage - wie löse ich den Transaktionsfehler, den ich bekomme? Muss ich meinen Kontext jedes Mal durch die Schleifen löschen und neu erstellen (ergibt für mich keinen Sinn)?


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

Ich habe herausgefunden, dass meine Anfrage falsch war und dass mehr als 250 Änderungen anhängig waren. Also habe ich meine Anfrage korrigiert, und jetzt funktioniert es korrekt.

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.


Ich bin viel zu spät zur Party, aber heute sah ich mich dem gleichen Fehler gegenüber und wie ich es gelöst habe, war einfach. Mein Szenario war ähnlich zu diesem gegebenen Code, den ich DB-Transaktionen innerhalb verschachtelter for-each-Schleifen machte.

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

Für die oben genannten Szenarien wird die Lösung so aussehen:

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 ### //
                    }
                }

Also in dem Projekt, in dem ich genau dieses Problem hatte, war das Problem nicht in der foreach oder der .toList() es war tatsächlich in der AutoFac-Konfiguration, die wir verwendet haben. Dies verursachte einige seltsame Situationen, in denen der obige Fehler ausgelöst wurde, aber auch eine Menge anderer äquivalenter Fehler geworfen wurden.

Das war unser Fix: Dies wurde 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();

In meinem Fall tauchte das Problem auf, als ich Stored Procedure über EF anrief und später SaveChanges diese Ausnahme auslöst. 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
   }
}

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

Durch das Aufrufen der SaveChanges () -Methode wird eine Transaktion gestartet, die automatisch alle Änderungen in der Datenbank zurücksetzt, wenn vor dem Abschluss der Iteration eine Ausnahme auftritt. Andernfalls wird die Transaktion bestätigt. Sie könnten versucht sein, die Methode nach jeder Entitätsaktualisierung oder -löschung und nicht erst nach Abschluss der Iteration anzuwenden, insbesondere wenn Sie eine große Anzahl von Entitäten aktualisieren oder löschen.

Wenn Sie versuchen, SaveChanges () aufzurufen, bevor alle Daten verarbeitet wurden, wird eine Ausnahme "Neue Transaktion ist nicht zulässig, da andere Threads in der Sitzung ausgeführt werden" ausgelöst. Die Ausnahme tritt auf, da SQL Server es nicht erlaubt, eine neue Transaktion für eine Verbindung zu starten, bei der ein SqlDataReader geöffnet ist, auch wenn mehrere aktive Datensatzgruppen (MARS) durch die Verbindungszeichenfolge aktiviert sind (EFs Standardverbindungszeichenfolge aktiviert MARS)

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


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


Wir haben jetzt eine offizielle Antwort auf den Bug, der bei Connect geöffnet wurde, gepostet. Die empfohlenen Umgehungen lauten wie folgt:

Dieser Fehler ist darauf zurückzuführen, dass Entity Framework während des SaveChanges () - Aufrufs eine implizite Transaktion erstellt hat. Der beste Weg, um den Fehler zu umgehen, besteht darin, ein anderes Muster zu verwenden (dh nicht während des Lesens zu speichern) oder explizit eine Transaktion 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();
    }
} 

Ich bekam das gleiche Problem, aber in einer anderen Situation. Ich hatte eine Liste von Gegenständen in einer Listbox. Der Benutzer kann auf ein Element klicken und Löschen auswählen, aber ich verwende ein gespeichertes Proc, um das Element zu löschen, da beim Löschen des Elements viel Logik beteiligt ist. Wenn ich den gespeicherten Proc anrufe, funktioniert das Löschen gut, aber jeder zukünftige Aufruf von SaveChanges wird den Fehler verursachen. Meine Lösung war, das gespeicherte Proc außerhalb von EF aufzurufen, und das funktionierte gut. Aus irgendeinem Grund, wenn ich den gespeicherten Proc unter Verwendung der EF-Methode anrufe, bleibt etwas offen.


Immer Verwenden Sie Ihre Auswahl als Liste

Z.B:

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

Dann Schleife durch die Sammlung, während Änderungen gespeichert werden

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

             }

Ich musste ein riesiges ResultSet lesen und einige Datensätze in der Tabelle aktualisieren. Ich habe versucht, Stücke zu verwenden, wie es in Drew Noakes ' answer .

Leider habe ich nach 50000 Aufzeichnungen OutofMemoryException. Die Antwort Entity Framework großer Datensatz, out of memory 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 hält diese zweite Menge für die Lebensdauer des Kontexts und diese Menge, die Sie aus dem Speicher rennt.

Die Empfehlung besteht darin, den Kontext jeder Charge zu erneuern.

Also habe ich minimale und maximale Werte des Primärschlüssels abgerufen - die Tabellen haben Primärschlüssel als autoincrementale Ganzzahlen. Dann habe ich Datensätze aus der Datenbank abgerufen, indem ich den Kontext für jeden Block geöffnet habe. Nach der Verarbeitung schließt sich der Chunk-Kontext und gibt den Speicher frei. Es stellt sicher, dass die Speichernutzung nicht zunimmt.

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 den Eigenschaften Von und Bis.


Wie Sie bereits festgestellt haben, können Sie nicht von einer foreach aus speichern, die immer noch über einen aktiven Reader aus der Datenbank zieht.

Das Aufrufen von ToList() oder ToArray() ist gut für kleine Datensätze, aber wenn Sie Tausende von Zeilen haben, verbrauchen Sie eine große Menge an Speicher.

Es ist besser, die Zeilen in Chunks 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++;
        }
    }
}

Angesichts der oben genannten Erweiterungsmethoden können Sie Ihre Abfrage wie folgt schreiben:

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

Das abfragbare Objekt, auf das Sie diese Methode anwenden, muss geordnet sein. Dies liegt daran, dass Entity Framework nur IQueryable<T>.Skip(int) bei geordneten Abfragen unterstützt, was sinnvoll ist, wenn Sie berücksichtigen, dass mehrere Abfragen für verschiedene Bereiche erfordern, dass die Reihenfolge stabil ist. Wenn Ihnen die Reihenfolge nicht wichtig ist, ordnen Sie einfach nach Primärschlüssel an, da wahrscheinlich ein Clustered-Index vorhanden ist.

Diese Version SaveChanges() die Datenbank in SaveChanges() . Beachten Sie, dass SaveChanges() für jede Entität aufgerufen wird.

Wenn Sie Ihren Durchsatz drastisch verbessern möchten, sollten Sie SaveChanges() seltener aufrufen. 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 kommst du immer noch weit voraus. Ihre Laufleistung kann variieren, aber das war für mich Welten schneller.

Und es kommt um die Ausnahme, die Sie gesehen haben.

BEARBEITEN Ich habe diese Frage nach dem Ausführen von SQL Profiler noch einmal gelesen und einige Dinge aktualisiert, um die Leistung zu verbessern. Für alle, die interessiert sind, hier ist ein Beispiel SQL, das zeigt, was von der DB 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

Nachfolgende Aufrufe müssen vorherige Ergebnisblöcke überspringen, row_number 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

Ich weiß, dass es eine alte Frage ist, aber ich habe diesen Fehler heute gesehen.

und ich habe festgestellt, dass dieser Fehler ausgelöst werden kann, wenn ein Datenbank-Tabellen-Trigger einen Fehler erhält.

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


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




transactions