Entity Framework: esiste già un DataReader aperto associato a questo comando


285

Sto usando Entity Framework e occasionalmente ottengo questo errore.

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

Anche se non sto eseguendo alcuna gestione manuale delle connessioni.

questo errore si verifica in modo intermittente.

codice che attiva l'errore (abbreviato per facilitare la lettura):

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

utilizzando il modello Dispose per aprire ogni volta una nuova connessione.

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

}

ancora problematico

perché EF non riutilizzerebbe una connessione se è già aperta?


1
Mi rendo conto che questa domanda è antica, ma sarei interessato a sapere che tipo sono la tua predicatee le historicPredicatevariabili. Ho scoperto che se passi Func<T, bool>ad Where()esso verrà compilato e talvolta funzionerà (perché fa il "dove" in memoria). Quello che dovresti fare è passare Expression<Func<T, bool>>a Where().
James,

Risposte:


351

Non si tratta di chiudere la connessione. EF gestisce correttamente la connessione. La mia comprensione di questo problema è che ci sono più comandi di recupero dati eseguiti su una singola connessione (o singolo comando con più selezioni) mentre il successivo DataReader viene eseguito prima che il primo abbia completato la lettura. L'unico modo per evitare l'eccezione è consentire più DataReaders nidificati = attivare MultipleActiveResultSet. Un altro scenario in cui ciò accade sempre è quando si scorre il risultato della query (IQueryable) e si avvia il caricamento lazy per l'entità caricata all'interno dell'iterazione.


2
avrebbe senso. ma esiste una sola selezione all'interno di ciascun metodo.
Sonic Soul

1
@Sonic: questa è la domanda. Forse c'è più di un comando eseguito ma non lo vedi. Non sono sicuro se questo può essere rintracciato in Profiler (l'eccezione può essere generata prima dell'esecuzione del secondo lettore). Puoi anche provare a trasmettere la query a ObjectQuery e chiamare ToTraceString per visualizzare il comando SQL. È difficile da rintracciare. Accendo sempre MARS.
Ladislav Mrnka,

2
@Sonic: nessuna mia intenzione era quella di controllare i comandi SQL eseguiti e completati.
Ladislav Mrnka,

11
ottimo, il mio problema era il secondo scenario: "quando si scorre il risultato della query (IQueryable) e si avvia il caricamento lento per l'entità caricata all'interno dell'iterazione".
Amr Elgarhy,

6
L'attivazione di MARS può avere apparentemente effetti collaterali negativi: designlimbo.com/?p=235
Søren Boisen

126

In alternativa all'utilizzo di MARS (MultipleActiveResultSets) è possibile scrivere il codice in modo da non aprire più set di risultati.

Quello che puoi fare è recuperare i dati in memoria, in questo modo non avrai il lettore aperto. È spesso causato dall'iterazione attraverso un set di risultati mentre si tenta di aprire un altro set di risultati.

Codice di esempio:

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

Diciamo che stai facendo una ricerca nel tuo database contenente questi:

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

Possiamo fare una soluzione semplice aggiungendo .ToList () in questo modo:

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

Ciò impone a entityframework di caricare l'elenco in memoria, quindi quando eseguiamo l'iterazione nel ciclo foreach non utilizza più il lettore di dati per aprire l'elenco, ma è invece in memoria.

Mi rendo conto che questo potrebbe non essere desiderato se si desidera scaricare alcune proprietà ad esempio. Questo è principalmente un esempio che, si spera, spiega come / perché potresti avere questo problema, così puoi prendere le decisioni di conseguenza


7
Questa soluzione ha funzionato per me. Aggiungi .ToList () subito dopo l'interrogazione e prima di fare qualsiasi altra cosa con il risultato.
TJKjaer,

9
Stai attento con questo e usa il buon senso. Se stai utilizzando ToListun migliaio di oggetti, aumenterà la memoria di una tonnellata. In questo esempio specifico, sarebbe meglio combinare la query interna con la prima, quindi viene generata solo una query anziché due.
kamranicus,

4
@subkamran Il mio punto era esattamente questo, pensare a qualcosa e scegliere ciò che è giusto per la situazione, non solo fare. L'esempio è solo qualcosa di casuale che ho pensato di spiegare :)
Jim Wolff,

3
Sicuramente, volevo solo segnalarlo esplicitamente per le persone felici di copiare / incollare :)
kamranicus

Non spararmi, ma questa non è in alcun modo una soluzione alla domanda. Da quando "il pull dei dati in memoria" è una soluzione per un problema relativo a SQL? Mi piace essere loquace con il database, quindi in nessun modo preferirei estrarre qualcosa in memoria "perché altrimenti viene generata un'eccezione SQL". Tuttavia, nel codice fornito, non c'è motivo di contattare il database due volte. Facile da eseguire in una sola chiamata. Fai attenzione a post come questo. ToList, First, Single, ... Dovrebbe essere usato solo quando i dati sono necessari in memoria (quindi solo i dati che VUOI), non quando altrimenti si verifica un'eccezione SQL.
Frederik Prijck,

70

C'è un altro modo per superare questo problema. Se è un modo migliore dipende dalla tua situazione.

Il problema deriva dal caricamento lento, quindi un modo per evitarlo non è quello di avere un caricamento lento, tramite l'uso di Includi:

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

Se si utilizzano i messaggi di posta elettronica appropriati Include, è possibile evitare di abilitare MARS. Ma se ne perdi uno, otterrai l'errore, quindi abilitare MARS è probabilmente il modo più semplice per risolverlo.


1
Ha funzionato come un fascino. .Includeè una soluzione molto migliore rispetto all'abilitazione di MARS e molto più semplice della scrittura del proprio codice di query SQL.
Nolonar,

15
Se qualcuno ha il problema che puoi scrivere solo .Include ("stringa") non un lambda, devi aggiungere "using System.Data.Entity" perché il metodo di estensione si trova lì.
Jim Wolff,

46

Viene visualizzato questo errore, quando la raccolta che si sta tentando di ripetere è un tipo di caricamento lento (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

La conversione della raccolta IQueriable in un'altra raccolta enumerabile risolverà questo problema. esempio

_dbContext.Users.ToList()

Nota: .ToList () crea un nuovo set ogni volta e può causare problemi di prestazioni se si ha a che fare con dati di grandi dimensioni.


1
La soluzione più semplice possibile! Big UP;)
Jacob Sobus,

1
Il recupero di elenchi illimitati può causare gravi problemi di prestazioni! Come può qualcuno votarlo?
SandRock,

1
@SandRock non per qualcuno che lavora per una piccola azienda - SELECT COUNT(*) FROM Users= 5
Simon_Weaver

5
Pensaci due volte. Un giovane sviluppatore che legge questo Q / A può pensare che questa sia una soluzione di tutti i tempi quando non lo è assolutamente. Ti suggerisco di modificare la tua risposta per avvertire i lettori del pericolo di recuperare elenchi illimitati da db.
SandRock,

1
@SandRock Penso che questo sarebbe un buon posto per collegare una risposta o un articolo che descriva le migliori pratiche.
Sinjai,

13

Ho risolto facilmente il problema (pragmatico) aggiungendo l'opzione al costruttore. Quindi, lo uso solo quando necessario.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

2
Grazie. Sta funzionando. Ho appena aggiunto MultipleActiveResultSets = true nella stringa di connessione direttamente in web.config
Mosharaf Hossain,

11

Prova a impostare la stringa di connessione MultipleActiveResultSets=true. Ciò consente il multitasking nel database.

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

Funziona per me ... se la tua connessione in app.config o la imposti a livello di programmazione ... lo spero utile


MultipleActiveResultSets = true aggiunto alla stringa di connessione probabilmente risolverà il problema. Questo non avrebbe dovuto essere sottovalutato.
Aaron Hudon,

sì, certo, ho dimostrato come aggiungere alla stringa di connessione
Mohamed Hocine,

4

Inizialmente avevo deciso di utilizzare un campo statico nella mia classe API per fare riferimento a un'istanza dell'oggetto MyDataContext (dove MyDataContext è un oggetto Context EF5), ma è quello che sembrava creare il problema. Ho aggiunto un codice simile al seguente a ciascuno dei miei metodi API e questo ha risolto il problema.

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

Come altre persone hanno affermato, gli oggetti Contesto dati EF NON sono thread-safe. Quindi posizionarli nell'oggetto statico alla fine causerà l'errore "lettore di dati" nelle giuste condizioni.

La mia ipotesi originale era che la creazione di una sola istanza dell'oggetto sarebbe stata più efficiente e avrebbe consentito una migliore gestione della memoria. Da quello che ho raccolto ricercando questo problema, non è così. In effetti, sembra essere più efficiente trattare ogni chiamata alla tua API come un evento isolato, thread-safe. Garantire che tutte le risorse vengano rilasciate correttamente, poiché l'oggetto non rientra nell'ambito.

Ciò ha senso soprattutto se porti la tua API alla progressione naturale successiva che sarebbe quella di esporla come WebService o API REST.

Divulgazione

  • Sistema operativo: Windows Server 2012
  • .NET: installato 4.5, progetto utilizzando 4.0
  • Fonte dei dati: MySQL
  • Framework applicativo: MVC3
  • Autenticazione: moduli

3

Ho notato che questo errore si verifica quando invio un IQueriable alla vista e lo uso in una doppia foreach, dove anche la foreach interna deve utilizzare la connessione. Esempio semplice (ViewBag.parents può essere IQueriable o DbSet):

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

    }
}

La soluzione semplice è utilizzare .ToList()la raccolta prima di utilizzarla. Si noti inoltre che MARS non funziona con MySQL.


GRAZIE! Tutto qui diceva "il problema è rappresentato dai loop nidificati", ma nessuno ha detto come risolverlo. Ho effettuato una ToList()prima chiamata per ottenere una raccolta dal DB. Poi ho fatto una foreachlista e le chiamate successive hanno funzionato perfettamente invece di dare l'errore.
AlbatrossCafe

@AlbatrossCafe ... ma nessuno menziona che in quel caso i tuoi dati verranno caricati in memoria e la query verrà eseguita in memoria, anziché DB
Lightning3

3

Ho scoperto che avevo lo stesso errore e si è verificato quando stavo usando un Func<TEntity, bool>invece di unExpression<Func<TEntity, bool>> per il tuo predicate.

Una volta ho cambiato tutto Func's per Expression'sl'eccezione smesso di essere buttato.

Credo che EntityFramworkfaccia alcune cose intelligenti con Expression'scui semplicemente non ha a che fareFunc's


Ciò richiede più voti. Stavo cercando di creare un metodo nella mia classe DataContext prendendo in (MyTParent model, Func<MyTChildren, bool> func)modo che i miei ViewModels potessero specificare una certa whereclausola al metodo Generic DataContext. Niente funzionava fino a quando non l'ho fatto.
Giustino,

3

2 soluzioni per mitigare questo problema:

  1. Forzare la memorizzazione nella cache della memoria mantenendo il caricamento lento .ToList()dopo la query, in modo da poter scorrere attraverso l'apertura di un nuovo DataReader.
  2. .Include(/ entità aggiuntive che si desidera caricare nella query /) questo si chiama caricamento desideroso, che consente di (effettivamente) includere oggetti associati (entità) durante l'esecuzione di una query con DataReader.

2

Una buona via di mezzo tra l'abilitazione di MARS e il recupero dell'intero set di risultati in memoria consiste nel recuperare solo gli ID in una query iniziale e quindi scorrere gli ID materializzando ciascuna entità mentre si procede.

Ad esempio (utilizzando le entità di esempio "Blog e post" come in questa risposta ):

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

Ciò significa che si trascinano in memoria solo poche migliaia di numeri interi, a differenza di migliaia di interi grafici di oggetti, il che dovrebbe ridurre al minimo l'utilizzo della memoria, consentendo al contempo di lavorare elemento per articolo senza abilitare MARS.

Un altro vantaggio di questo, come visto nell'esempio, è che puoi salvare le modifiche mentre esegui il ciclo attraverso ogni elemento, invece di dover aspettare fino alla fine del ciclo (o qualche altra soluzione alternativa), come sarebbe necessario anche con MARS abilitato (vedi qui e qui ).


context.SaveChanges();inside loop :(. Non va bene. Deve essere al di fuori del loop.
Jawand Singh

1

Nel mio caso ho scoperto che mancavano le istruzioni "wait" prima delle chiamate myContext.SaveChangesAsync (). L'aggiunta di wait prima di quelle chiamate asincrone risolveva i problemi del lettore di dati per me.


0

Se proviamo a raggruppare parte delle nostre condizioni in un Func <> o in un metodo di estensione otterremo questo errore, supponiamo di avere un codice come questo:

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) { .... }

Questo genererà un'eccezione se proviamo a usarlo in un Where (), ciò che dovremmo fare invece è costruire un Predicato come questo:

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

Ulteriori informazioni sono disponibili all'indirizzo: http://www.albahari.com/nutshell/predicatebuilder.aspx


0

Questo problema può essere risolto semplicemente convertendo i dati in un elenco

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

ToList () effettua la chiamata ma il codice sopra non dispone ancora della connessione. quindi il tuo _webcontext è ancora a rischio di essere chiuso al momento della linea 1
Sonic Soul

0

Nella mia situazione il problema si è verificato a causa di una registrazione di iniezione di dipendenza. Stavo iniettando un servizio scope per richiesta che utilizzava un dbcontext in un servizio registrato singleton. Pertanto il dbcontext è stato utilizzato all'interno di una richiesta multipla e quindi l'errore.


0

Nel mio caso il problema non aveva nulla a che fare con la stringa di connessione MARS ma con la serializzazione json. Dopo aver aggiornato il mio progetto da NetCore2 a 3 ho ricevuto questo errore.

Ulteriori informazioni possono essere trovate qui


-6

Ho risolto questo problema utilizzando la seguente sezione di codice prima della seconda query:

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

puoi modificare il tempo di sonno in millisecondi

PD Utile quando si usano discussioni


13
Aggiunta arbitraria di Thread. Dormire in qualsiasi soluzione è una cattiva pratica - ed è particolarmente negativo quando viene utilizzato per eludere un problema diverso in cui lo stato di un certo valore non è del tutto compreso. Avrei pensato che "Usare i thread" come indicato nella parte inferiore della risposta significherebbe avere almeno una conoscenza di base del threading, ma questa risposta non tiene conto di alcun contesto, specialmente quelle circostanze in cui è una pessima idea per usare Thread.Sleep - come su un thread dell'interfaccia utente.
Mike Tours,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.