Entity Framework: Diesem Befehl ist bereits ein offener DataReader zugeordnet

285

Ich verwende Entity Framework und gelegentlich erhalte ich diesen Fehler.

EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

Auch wenn ich kein manuelles Verbindungsmanagement mache.

Dieser Fehler tritt zeitweise auf.

Code, der den Fehler auslöst (zur besseren Lesbarkeit verkürzt):

        if (critera.FromDate > x) {
            t= _tEntitites.T.Where(predicate).ToList();
        }
        else {
            t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
        }

Verwenden Sie Muster entsorgen, um jedes Mal eine neue Verbindung zu öffnen.

using (_tEntitites = new TEntities(GetEntityConnection())) {

    if (critera.FromDate > x) {
        t= _tEntitites.T.Where(predicate).ToList();
    }
    else {
        t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
    }

}

immer noch problematisch

Warum sollte EF eine Verbindung nicht wiederverwenden, wenn sie bereits geöffnet ist?

Sonic Soul
quelle
1
Mir ist klar, dass diese Frage uralt ist, aber ich würde gerne wissen, welchen Typ Ihre predicateund Ihre historicPredicateVariablen haben. Ich habe entdeckt , dass , wenn Sie passieren Func<T, bool>zu Where()wird es kompilieren und manchmal Arbeit (weil es funktioniert das „wo“ im Speicher). Was Sie tun sollten , ist Expression<Func<T, bool>>zu übergeben Where().
James

Antworten:

351

Es geht nicht darum, die Verbindung zu schließen. EF verwaltet die Verbindung korrekt. Mein Verständnis dieses Problems besteht darin, dass mehrere Datenabrufbefehle für eine einzelne Verbindung (oder einen einzelnen Befehl mit mehreren Auswahlen) ausgeführt werden, während der nächste DataReader ausgeführt wird, bevor der erste den Lesevorgang abgeschlossen hat. Die einzige Möglichkeit, die Ausnahme zu vermeiden, besteht darin, mehrere verschachtelte DataReaders zuzulassen = MultipleActiveResultSets zu aktivieren. Ein anderes Szenario, in dem dies immer der Fall ist, besteht darin, dass Sie das Ergebnis der Abfrage (IQueryable) durchlaufen und ein verzögertes Laden für die geladene Entität innerhalb der Iteration auslösen.

Ladislav Mrnka
quelle
2
das würde Sinn machen. Innerhalb jeder Methode gibt es jedoch nur eine Auswahl.
Sonic Soul
1
@Sonic: Das ist die Frage. Möglicherweise wird mehr als ein Befehl ausgeführt, aber Sie sehen ihn nicht. Ich bin nicht sicher, ob dies in Profiler verfolgt werden kann (Ausnahme kann ausgelöst werden, bevor der zweite Leser ausgeführt wird). Sie können auch versuchen, die Abfrage in ObjectQuery umzuwandeln und ToTraceString aufzurufen, um den SQL-Befehl anzuzeigen. Es ist schwer zu verfolgen. Ich schalte immer MARS ein.
Ladislav Mrnka
2
@Sonic: Nein, ich wollte ausgeführte und abgeschlossene SQL-Befehle überprüfen.
Ladislav Mrnka
11
Großartig, mein Problem war das zweite Szenario: "Wenn Sie das Ergebnis der Abfrage (IQueryable) durchlaufen und ein verzögertes Laden für die geladene Entität innerhalb der Iteration auslösen."
Amr Elgarhy
6
Das Aktivieren von MARS kann anscheinend schlimme Nebenwirkungen haben: designlimbo.com/?p=235
Søren Boisen
126

Alternativ zur Verwendung von MARS (MultipleActiveResultSets) können Sie Ihren Code so schreiben, dass Sie nicht mehrere Ergebnismengen öffnen.

Sie können die Daten in den Speicher abrufen, sodass der Reader nicht geöffnet ist. Dies wird häufig durch das Durchlaufen einer Ergebnismenge beim Versuch, eine andere Ergebnismenge zu öffnen, verursacht.

Beispielcode:

public class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogID { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int PostID { get; set; }
    public virtual Blog Blog { get; set; }
    public string Text { get; set; }
}

Nehmen wir an, Sie führen eine Suche in Ihrer Datenbank durch, die Folgendes enthält:

var context = new MyContext();

//here we have one resultset
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5); 

foreach (var blog in largeBlogs) //we use the result set here
{
     //here we try to get another result set while we are still reading the above set.
    var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Important Text"));
}

Wir können eine einfache Lösung dafür finden, indem wir .ToList () wie folgt hinzufügen :

var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();

Dies zwingt entityframework, die Liste in den Speicher zu laden. Wenn wir sie also in der foreach-Schleife durchlaufen, wird der Datenleser nicht mehr zum Öffnen der Liste verwendet, sondern im Speicher.

Mir ist klar, dass dies möglicherweise nicht erwünscht ist, wenn Sie beispielsweise einige Eigenschaften verzögern möchten. Dies ist meistens ein Beispiel, das hoffentlich erklärt, wie / warum Sie dieses Problem bekommen könnten, damit Sie Entscheidungen entsprechend treffen können

Jim Wolff
quelle
7
Diese Lösung hat bei mir funktioniert. Fügen Sie .ToList () direkt nach der Abfrage und bevor Sie etwas anderes mit dem Ergebnis tun, hinzu.
TJKjaer
9
Seien Sie vorsichtig damit und verwenden Sie den gesunden Menschenverstand. Wenn Sie ToListtausend Objekte bearbeiten, erhöht sich der Speicher um eine Tonne. In diesem speziellen Beispiel ist es besser, die innere Abfrage mit der ersten zu kombinieren, damit nur eine Abfrage generiert wird und nicht zwei.
Kamranicus
4
@subkamran Mein Punkt war genau das, über etwas nachzudenken und zu wählen, was für die Situation richtig ist, nicht nur zu tun. Das Beispiel ist nur etwas Zufälliges, das ich mir ausgedacht habe, um es zu erklären :)
Jim Wolff
3
Auf jeden Fall wollte ich nur ausdrücklich darauf hinweisen, dass Leute gerne kopieren / einfügen :)
kamranicus
Erschieß mich nicht, aber das ist in keiner Weise eine Lösung für die Frage. Seit wann ist "Daten in den Speicher ziehen" eine Lösung für ein SQL-Problem? Ich mag es, mit der Datenbank zu plaudern, also würde ich es auf keinen Fall vorziehen, etwas in den Speicher zu ziehen, "weil sonst eine SQL-Ausnahme ausgelöst wird". In Ihrem Code gibt es jedoch keinen Grund, die Datenbank zweimal zu kontaktieren. Einfach in einem Anruf zu erledigen. Seien Sie vorsichtig mit solchen Posts. ToList, First, Single, ... Sollte nur verwendet werden, wenn die Daten im Speicher benötigt werden (also nur die Daten, die Sie WOLLEN), nicht, wenn andernfalls eine SQL-Ausnahme auftritt.
Frederik Prijck
70

Es gibt einen anderen Weg, um dieses Problem zu überwinden. Ob es ein besserer Weg ist, hängt von Ihrer Situation ab.

Das Problem ergibt sich aus dem verzögerten Laden. Eine Möglichkeit, dies zu vermeiden, besteht darin, kein verzögertes Laden mithilfe von Include zu verwenden:

var results = myContext.Customers
    .Include(x => x.Orders)
    .Include(x => x.Addresses)
    .Include(x => x.PaymentMethods);

Wenn Sie die entsprechenden Includes verwenden, können Sie die Aktivierung von MARS vermeiden. Wenn Sie jedoch einen verpassen, wird der Fehler angezeigt. Daher ist die Aktivierung von MARS wahrscheinlich der einfachste Weg, ihn zu beheben.

Ryan Lundy
quelle
1
Lief wie am Schnürchen. .Includeist eine viel bessere Lösung als das Aktivieren von MARS und viel einfacher als das Schreiben eines eigenen SQL-Abfragecodes.
Nolonar
15
Wenn jemand das Problem hat, dass Sie nur .Include ("string") und kein Lambda schreiben können, müssen Sie "using System.Data.Entity" hinzufügen, da sich dort die Erweiterungsmethode befindet.
Jim Wolff
46

Sie erhalten diesen Fehler, wenn die Sammlung, die Sie iterieren möchten, eine Art verzögertes Laden ist (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

Das Konvertieren der IQueriable-Sammlung in eine andere aufzählbare Sammlung löst dieses Problem. Beispiel

_dbContext.Users.ToList()

Hinweis: .ToList () erstellt jedes Mal einen neuen Satz und kann zu Leistungsproblemen führen, wenn Sie mit großen Datenmengen arbeiten.

Nalan Madheswaran
quelle
1
Die einfachste Lösung! Big UP;)
Jacob Sobus
1
Das Abrufen unbegrenzter Listen kann schwerwiegende Leistungsprobleme verursachen! Wie kann jemand das unterstützen?
SandRock
1
@ SandRock nicht für jemanden, der für eine kleine Firma arbeitet - SELECT COUNT(*) FROM Users= 5
Simon_Weaver
5
Überlegen Sie zweimal darüber. Ein junger Entwickler, der diese Frage / Antwort liest, könnte denken, dass dies eine Allzeitlösung ist, wenn dies absolut nicht der Fall ist. Ich schlage vor, Sie bearbeiten Ihre Antwort, um die Leser vor der Gefahr zu warnen, unbegrenzte Listen von db abzurufen.
SandRock
1
@ SandRock Ich denke, dies wäre ein guter Ort, um eine Antwort oder einen Artikel zu verlinken, in dem Best Practices beschrieben werden.
Sinjai
13

Ich habe das Problem leicht (pragmatisch) gelöst, indem ich dem Konstruktor die Option hinzugefügt habe. Daher benutze ich das nur bei Bedarf.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...
Harvey Triana
quelle
2
Danke dir. Es funktioniert. Ich habe gerade MultipleActiveResultSets = true in der Verbindungszeichenfolge direkt in web.config hinzugefügt
Mosharaf Hossain
11

Versuchen Sie in Ihrer Verbindungszeichenfolge festzulegen MultipleActiveResultSets=true. Dies ermöglicht Multitasking in der Datenbank.

Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

Das funktioniert bei mir ... ob Ihre Verbindung in app.config oder Sie sie programmgesteuert einstellen ... hoffe dies ist hilfreich

Mohamed Hocine
quelle
MultipleActiveResultSets = true, das zu Ihrer Verbindungszeichenfolge hinzugefügt wurde, wird das Problem wahrscheinlich beheben. Dies hätte nicht herabgestimmt werden dürfen.
Aaron Hudon
Ja sicher, ich habe gezeigt, wie man eine Verbindungszeichenfolge hinzufügt
Mohamed Hocine
4

Ich hatte ursprünglich beschlossen, ein statisches Feld in meiner API-Klasse zu verwenden, um auf eine Instanz des MyDataContext-Objekts zu verweisen (wobei MyDataContext ein EF5-Kontextobjekt ist), aber genau das schien das Problem zu verursachen. Ich habe jeder meiner API-Methoden Code wie den folgenden hinzugefügt, wodurch das Problem behoben wurde.

using(MyDBContext db = new MyDBContext())
{
    //Do some linq queries
}

Wie andere Personen angegeben haben, sind die EF-Datenkontextobjekte NICHT threadsicher. Wenn Sie sie also in das statische Objekt einfügen, wird der Fehler "Datenleser" unter den richtigen Bedingungen auftreten.

Meine ursprüngliche Annahme war, dass das Erstellen nur einer Instanz des Objekts effizienter ist und eine bessere Speicherverwaltung ermöglicht. Nach dem, was ich zu diesem Thema gesammelt habe, ist dies nicht der Fall. Tatsächlich scheint es effizienter zu sein, jeden Aufruf Ihrer API als isoliertes, threadsicheres Ereignis zu behandeln. Stellen Sie sicher, dass alle Ressourcen ordnungsgemäß freigegeben werden, da das Objekt den Gültigkeitsbereich verlässt.

Dies ist insbesondere dann sinnvoll, wenn Sie Ihre API zum nächsten natürlichen Fortschritt führen, bei dem sie als WebService- oder REST-API verfügbar gemacht wird.

Offenlegung

  • Betriebssystem: Windows Server 2012
  • .NET: Installiert 4.5, Projekt mit 4.0
  • Datenquelle: MySQL
  • Anwendungsframework: MVC3
  • Authentifizierung: Formulare
Jeffrey A. Gochin
quelle
3

Ich habe festgestellt, dass dieser Fehler auftritt, wenn ich ein IQueriable an die Ansicht sende und es in einem doppelten foreach verwende, wobei der innere foreach auch die Verbindung verwenden muss. Einfaches Beispiel (ViewBag.parents kann IQueriable oder DbSet sein):

foreach (var parent in ViewBag.parents)
{
    foreach (var child in parent.childs)
    {

    }
}

Die einfache Lösung besteht darin, .ToList()die Sammlung vor ihrer Verwendung zu verwenden. Beachten Sie auch, dass MARS nicht mit MySQL funktioniert.

cen
quelle
VIELEN DANK! Alles hier sagte "verschachtelte Schleifen sind das Problem", aber niemand sagte, wie man es behebt. Ich habe ToList()meinen ersten Anruf getätigt, um eine Sammlung von der DB zu erhalten. Dann habe ich eine foreachListe erstellt und die nachfolgenden Aufrufe haben einwandfrei funktioniert, anstatt den Fehler zu geben.
AlbatrossCafe
@AlbatrossCafe ... aber niemand erwähnt, dass in diesem Fall Ihre Daten in den Speicher geladen werden und die Abfrage im Speicher ausgeführt wird, anstatt DB
Lightning3
3

Ich stellte fest, dass ich den gleichen Fehler hatte, und er trat auf, als ich einen Func<TEntity, bool>anstelle eines Expression<Func<TEntity, bool>>für Ihren verwendete predicate.

Einmal änderte ich dir alle Func'szuExpression's die Ausnahme sie nicht mehr ausgelöst.

Ich glaube, das EntityFramworkmacht einige kluge Dinge, mit Expression'sdenen es einfach nicht zu tun hatFunc's

sQuir3l
quelle
Dies erfordert mehr Upvotes. Ich habe versucht, eine Methode in meiner DataContext-Klasse zu erstellen, die eine (MyTParent model, Func<MyTChildren, bool> func)berücksichtigt, damit meine ViewModels eine bestimmte whereKlausel für die generische DataContext-Methode angeben können . Nichts funktionierte, bis ich das tat.
Justin
3

2 Lösungen zur Minderung dieses Problems:

  1. .ToList()Erzwingen Sie, dass das Speicher-Caching nach Ihrer Abfrage verzögert geladen wird , damit Sie es durchlaufen und einen neuen DataReader öffnen können.
  2. .Include(/ zusätzliche Entitäten, die Sie in die Abfrage laden möchten /) Dies wird als eifriges Laden bezeichnet, mit dem Sie (tatsächlich) zugeordnete Objekte (Entitäten) während der Ausführung einer Abfrage mit dem DataReader einbeziehen können.
Stefano Beltrame
quelle
2

Ein guter Mittelweg zwischen dem Aktivieren von MARS und dem Abrufen der gesamten Ergebnismenge im Speicher besteht darin, nur IDs in einer anfänglichen Abfrage abzurufen und dann die IDs zu durchlaufen, die jede Entität materialisieren, während Sie fortfahren.

Zum Beispiel (unter Verwendung der Beispielentitäten "Blog and Posts" wie in dieser Antwort ):

using (var context = new BlogContext())
{
    // Get the IDs of all the items to loop through. This is
    // materialized so that the data reader is closed by the
    // time we're looping through the list.
    var blogIds = context.Blogs.Select(blog => blog.Id).ToList();

    // This query represents all our items in their full glory,
    // but, items are only materialized one at a time as we
    // loop through them.
    var blogs =
        blogIds.Select(id => context.Blogs.First(blog => blog.Id == id));

    foreach (var blog in blogs)
    {
        this.DoSomethingWith(blog.Posts);

        context.SaveChanges();
    }
}

Dies bedeutet, dass Sie nur einige tausend Ganzzahlen in den Speicher ziehen, im Gegensatz zu Tausenden ganzer Objektdiagramme. Dies sollte die Speichernutzung minimieren und es Ihnen ermöglichen, Element für Element zu arbeiten, ohne MARS zu aktivieren.

Ein weiterer netter Vorteil davon ist, wie Sie im Beispiel sehen können, dass Sie Änderungen speichern können, während Sie die einzelnen Elemente durchlaufen, anstatt bis zum Ende der Schleife (oder einer anderen solchen Problemumgehung) warten zu müssen, wie dies selbst bei erforderlich wäre MARS aktiviert (siehe hier und hier ).

Paul
quelle
context.SaveChanges();innere Schleife :(. Dies ist nicht gut. Es muss außerhalb der Schleife sein.
Jawand Singh
1

In meinem Fall stellte ich fest, dass vor den Aufrufen von myContext.SaveChangesAsync () Anweisungen zum Warten fehlten. Das Hinzufügen von Warten auf diese asynchronen Aufrufe hat die Probleme mit dem Datenleser für mich behoben.

Elijah Lofgren
quelle
0

Wenn wir versuchen, einen Teil unserer Bedingungen in eine Func <> - oder Erweiterungsmethode zu gruppieren, wird dieser Fehler angezeigt. Angenommen, wir haben einen Code wie den folgenden:

public static Func<PriceList, bool> IsCurrent()
{
  return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
              (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

Or

public static IEnumerable<PriceList> IsCurrent(this IEnumerable<PriceList> prices) { .... }

Dies löst die Ausnahme aus, wenn wir versuchen, sie in einem Where () zu verwenden. Stattdessen sollten wir ein Prädikat wie das folgende erstellen:

public static Expression<Func<PriceList, bool>> IsCurrent()
{
    return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
                (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

Weitere Informationen finden Sie unter: http://www.albahari.com/nutshell/predicatebuilder.aspx

Arvand
quelle
0

Dieses Problem kann einfach durch Konvertieren der Daten in eine Liste gelöst werden

 var details = _webcontext.products.ToList();


            if (details != null)
            {
                Parallel.ForEach(details, x =>
                {
                    Products obj = new Products();
                    obj.slno = x.slno;
                    obj.ProductName = x.ProductName;
                    obj.Price = Convert.ToInt32(x.Price);
                    li.Add(obj);

                });
                return li;
            }
Debendra Dash
quelle
Die ToList () ruft auf, aber der obige Code stellt die Verbindung immer noch nicht bereit. Ihr _webcontext ist also immer noch gefährdet, zum Zeitpunkt von Zeile 1
Sonic Soul
0

In meiner Situation trat das Problem aufgrund einer Registrierung der Abhängigkeitsinjektion auf. Ich habe einen Dienst pro Anforderungsbereich, der einen Datenbankkontext verwendet, in einen registrierten Singleton-Dienst eingefügt. Daher wurde der Datenbankkontext innerhalb einer Mehrfachanforderung und damit des Fehlers verwendet.

E. Staal
quelle
0

In meinem Fall hatte das Problem nichts mit der MARS-Verbindungszeichenfolge zu tun, sondern mit der JSON-Serialisierung. Nach dem Upgrade meines Projekts von NetCore2 auf 3 habe ich diesen Fehler erhalten.

Weitere Informationen finden Sie hier

Oder Else
quelle
-6

Ich habe dieses Problem mithilfe des folgenden Codeabschnitts vor der zweiten Abfrage gelöst:

 ...first query
 while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
 {
     System.Threading.Thread.Sleep(500);
 }
 ...second query

Sie können die Schlafzeit in Millisekunden ändern

PD Nützlich bei der Verwendung von Threads

i31nGo
quelle
13
Das willkürliche Hinzufügen von Thread.Sleep in einer Lösung ist eine schlechte Praxis - und besonders schlecht, wenn ein anderes Problem umgangen wird, bei dem der Status eines bestimmten Werts nicht vollständig verstanden wird. Ich hätte gedacht, dass "Verwenden von Threads", wie am Ende der Antwort angegeben, zumindest ein grundlegendes Verständnis des Threadings bedeuten würde - aber diese Antwort berücksichtigt keinen Kontext, insbesondere die Umstände, unter denen es eine sehr schlechte Idee ist Thread.Sleep verwenden - beispielsweise in einem UI-Thread.
Mike Tours