312 votes

Entity Framework: Il existe déjà un DataReader ouvert associé à cette Commande

Je suis en train d'utiliser Entity Framework et occasionnellement je vais obtenir cette erreur.

EntityCommandExecutionException
{"Il existe déjà un DataReader ouvert associé à cette Commande qui doit être fermé en premier."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

Même si je ne gère pas la connexion manuellement.

cette erreur se produit de manière intermittente.

code qui déclenche l'erreur (raccourci pour faciliter la lecture) :

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

utilisation du modèle Dispose pour ouvrir une nouvelle connexion à chaque fois.

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

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

}

toujours problématique

pourquoi EF ne réutiliserait-il pas une connexion si elle est déjà ouverte.

1 votes

Je réalise que cette question est ancienne, mais je serais intéressé de savoir quel type sont vos variables predicate et historicPredicate. J'ai découvert que si vous passez Func à Where(), cela compilera et fonctionnera parfois (car il fait le "where" en mémoire). Ce que vous devriez faire, c'est passer Expression> à Where().

388voto

Ladislav Mrnka Points 218632

Il ne s'agit pas de fermer la connexion. EF gère correctement la connexion. Ma compréhension de ce problème est qu'il y a plusieurs commandes de récupération des données exécutées sur une seule connexion (ou une seule commande avec plusieurs sélections) alors que le DataReader suivant est exécuté avant que le premier n'ait terminé la lecture. La seule façon d'éviter l'exception est de permettre plusieurs DataReaders imbriqués = activer MultipleActiveResultSets. Un autre scénario où cela se produit toujours est lorsque vous parcourez le résultat de la requête (IQueryable) et que vous déclencherez le chargement différé pour l'entité chargée à l'intérieur de l'itération.

2 votes

Cela aurait du sens. mais il n'y a qu'un seul choix dans chaque méthode.

1 votes

@Sonic: C'est la question. Il se peut qu'il y ait plus d'une commande exécutée mais que vous ne la voyez pas. Je ne suis pas sûr que cela puisse être tracé dans le Profiler (une exception peut être lancée avant l'exécution du deuxième lecteur). Vous pouvez également essayer de caster la requête en ObjectQuery et appeler ToTraceString pour voir la commande SQL. C'est difficile à suivre. J'active toujours MARS.

0 votes

Je ne suis pas sûr de savoir comment le traçage pourrait aider... cherchons-nous à voir si EF ferme la connexion après chaque appel ? Ne devrait-il pas la recycler ou utiliser un pool de connexions ? Dans tous les cas, que devrais-je chercher dans une trace ?

146voto

FRoZeN Points 1750

Alternativement à l'utilisation de MARS (MultipleActiveResultSets), vous pouvez écrire votre code de sorte que vous n'ouvrez pas plusieurs ensembles de résultats.

Ce que vous pouvez faire, c'est récupérer les données en mémoire, de cette façon le lecteur ne sera pas ouvert. Cela est souvent causé par le fait de parcourir un jeu de résultats tout en essayant d'ouvrir un autre jeu de résultats.

Code d'Exemple :

public class MyContext : DbContext
{
    public DbSet Blogs { get; set; }
    public DbSet Posts { get; set; }
}

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

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

Disons que vous faites une recherche dans votre base de données contenant ces données :

var context = new MyContext();

// ici nous avons un jeu de résultats
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5); 

foreach (var blog in largeBlogs) // nous utilisons le jeu de résultats ici
{
     // ici nous essayons de récupérer un autre jeu de résultats alors que nous sommes encore en train de lire le jeu ci-dessus.
    var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Texte Important"));
}

Nous pouvons apporter une solution simple à cela en ajoutant .ToList() comme ceci :

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

Cela force EntityFramework à charger la liste en mémoire, donc lorsque nous la parcourons dans la boucle foreach, elle n'utilise plus le lecteur de données pour ouvrir la liste, elle se trouve plutôt en mémoire.

Je réalise que cela peut ne pas être souhaité si vous voulez par exemple effectuer un chargement paresseux de certaines propriétés. Il s'agit principalement d'un exemple qui explique espérons-le comment/pourquoi vous pourriez rencontrer ce problème, afin que vous puissiez prendre des décisions en conséquence

9 votes

Cette solution a fonctionné pour moi. Ajoutez .ToList() juste après la requête et avant de faire quoi que ce soit d'autre avec le résultat.

12 votes

Faites attention à cela et utilisez votre bon sens. Si vous êtes en train de ToLister mille objets, cela va augmenter énormément la mémoire. Dans cet exemple spécifique, il serait préférable de combiner la requête interne avec la première afin de générer une seule requête au lieu de deux.

4 votes

@subkamran Mon point était exactement celui-là, réfléchir à quelque chose et choisir ce qui est bien pour la situation, pas seulement faire. L'exemple est simplement quelque chose d'aléatoire que j'ai pensé pour expliquer :)

76voto

Kyralessa Points 76456

Il existe une autre façon de surmonter ce problème. Que ce soit une meilleure façon dépend de votre situation.

Le problème résulte du chargement paresseux, donc une façon d'éviter cela est de ne pas avoir de chargement paresseux, grâce à l'utilisation de Include:

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

Si vous utilisez les Include appropriés, vous pouvez éviter d'activer Mars. Mais si vous en manquez un, vous obtiendrez l'erreur, donc activer Mars est probablement le moyen le plus simple de le corriger.

1 votes

A parfaitement fonctionné. .Include est une solution bien meilleure que d'activer MARS et bien plus facile que d'écrire votre propre code de requête SQL.

19 votes

Si quelqu'un rencontre le problème selon lequel vous ne pouvez écrire que le .Include("string") et non pas un lambda, vous devez ajouter "using System.Data.Entity" car la méthode d'extension se trouve là.

57voto

Nalan M Points 834

Vous obtenez cette erreur lorsque la collection que vous essayez d'itérer est de type lazy loading (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

Convertir la collection IQueriable en une autre collection énumérable résoudra ce problème. exemple

_dbContext.Users.ToList()

Remarque: .ToList() crée un nouvel ensemble à chaque fois et cela peut causer des problèmes de performances si vous travaillez avec des données volumineuses.

2 votes

La solution la plus simple possible! Big UP ;)

2 votes

Récupérer des listes non bornées peut entraîner des problèmes de performance graves! Comment quelqu'un peut voter pour ça?

0 votes

Arracher les cheveux... ToList... réussite.

16voto

Harvey Triana Points 73

J'ai résolu le problème facilement (de manière pragmatique) en ajoutant l'option au constructeur. Ainsi, je l'utilise uniquement quand c'est nécessaire.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* votre chaîne de connexion */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

2 votes

Merci. Ça fonctionne. J'ai juste ajouté le MultipleActiveResultSets=true dans la chaîne de connexion directement dans le web.config

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X