web-dev-qa-db-fra.com

Entity Framework: un DataReader ouvert est déjà associé à cette commande

J'utilise Entity Framework et de temps en temps j'obtiens cette erreur. 

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

Même si je ne fais pas de gestion de connexion manuelle. 

cette erreur se produit par intermittence.

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

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

utiliser le 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<T>(_tEntitites.TA.Where(historicPredicate).ToList());
    }

}

toujours problématique 

pourquoi EF ne réutiliserait-il pas une connexion si celle-ci est déjà ouverte? 

263
Sonic Soul

Il ne s'agit pas de fermer la connexion. EF gère la connexion correctement. D'après ce que je comprends de ce problème, il existe plusieurs commandes d'extraction de données exécutées sur une seule connexion (ou une commande unique avec plusieurs sélections) pendant l'exécution de DataReader avant la première lecture. Le seul moyen d'éviter l'exception consiste à autoriser 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éclenchez le chargement différé de l'entité chargée dans l'itération.

330
Ladislav Mrnka

Au lieu d'utiliser MARS (MultipleActiveResultSets), vous pouvez écrire votre code pour ne pas ouvrir plusieurs jeux de résultats.

Ce que vous pouvez faire est de récupérer les données dans la mémoire, de manière à ne pas ouvrir le lecteur .. Cela est souvent causé par une itération dans un jeu de résultats lors de la tentative d'ouverture d'un autre jeu de résultats.

Exemple de code:

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

Disons que vous effectuez une recherche dans votre base de données contenant ces éléments:

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

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

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

Ceci oblige entityframework à charger la liste en mémoire. Ainsi, lorsque nous effectuons une itération dans la boucle foreach, il n’utilise plus le lecteur de données pour ouvrir la liste, mais bien en mémoire.

Je me rends compte que cela peut ne pas être souhaité si vous voulez charger certaines propriétés, par exemple. Ceci est principalement un exemple qui explique, espérons-le, comment/pourquoi vous pourriez avoir ce problème, afin que vous puissiez prendre des décisions en conséquence

117
Jim Wolff

Il existe un autre moyen de surmonter ce problème. Que ce soit un meilleur moyen dépend de votre situation.

Le problème provient d’un chargement paresseux. L’un des moyens de l’éviter consiste à ne pas utiliser de chargement paresseux, en utilisant Include:

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

Si vous utilisez la variable Includes appropriée, vous pouvez éviter d'activer MARS. Mais si vous en manquez un, vous obtiendrez l'erreur. Activer MARS est donc probablement le moyen le plus simple de le réparer.

65
Ryan Lundy

Vous obtenez cette erreur lorsque la collection que vous essayez d'itérer est une sorte de chargement paresseux (IQueriable). 

foreach (var user in _dbContext.Users)
{    
}

La conversion de la collection IQueriable en une autre collection énumérable résoudra ce problème. 

_dbContext.Users.ToList()

Remarque: .ToList () crée un nouvel ensemble à chaque fois et peut entraîner des problèmes de performances si vous traitez des données volumineuses.

39

J'ai facilement résolu le problème (pragmatique) en ajoutant l'option au constructeur. Ainsi, je ne l’utilise que lorsque cela est nécessaire. 

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

essayez dans votre chaîne de connexion de définir "MultipleActiveResultSets = true" cela autorise le multitâche sur la base de données . "Server = yourserver; AttachDbFilename = base de données; ID utilisateur = sa; Mot de passe = blah; MultipleActiveResultSets = true; App = EntityFramework" ça marche pour moi ... que votre connexion dans app.config ou que vous la configuriez par programme ... espérons que cela vous aidera 

5
Mohamed Hocine

J'avais initialement décidé d'utiliser un champ statique dans ma classe d'API pour référencer une instance de l'objet MyDataContext (Où MyDataContext est un objet Contexte EF5), mais c'est ce qui semblait créer le problème. J'ai ajouté un code semblable à celui-ci à chacune de mes méthodes d'API et cela a résolu le problème. 

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

Comme d'autres personnes l'ont déclaré, les objets Contexte de données EF ne sont pas thread-safe. Donc, les placer dans l'objet statique causera éventuellement l'erreur "lecteur de données" dans les bonnes conditions. 

Mon hypothèse de départ était que la création d'une seule instance de l'objet serait plus efficace et permettrait une meilleure gestion de la mémoire. D'après ce que j'ai recueilli en recherchant ce problème, ce n'est pas le cas. En fait, il semble plus efficace de traiter chaque appel de votre API comme un événement isolé et sécurisé pour les threads. S'assurer que toutes les ressources sont correctement libérées, car l'objet sort de la portée.

Cela est particulièrement utile si vous amenez votre API à la prochaine progression naturelle, à savoir l'exposer sous forme d'API WebService ou REST.

Divulgation

  • Système d'exploitation: Windows Server 2012
  • .NET: installé 4.5, projet utilisant 4.0
  • Source de données: MySQL
  • Cadre d'application: MVC3
  • Authentification: Formulaires
4
Jeffrey A. Gochin

J'ai remarqué que cette erreur se produit lorsque j'envoie un IQueriable à la vue et que je l'utilise dans un double foreach, où le foreach interne doit également utiliser la connexion. Exemple simple (les ViewBag.parents peuvent être IQueriable ou DbSet):

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

    }
}

La solution simple consiste à utiliser .ToList() sur la collection avant de l’utiliser. Notez également que MARS ne fonctionne pas avec MySQL.

3
cen

Un bon compromis entre l'activation de MARS et la récupération de l'ensemble des résultats en mémoire consiste à extraire uniquement les ID d'une requête initiale, puis de parcourir les ID matérialisant chaque entité au fur et à mesure.

Par exemple (en utilisant les exemples d’entités "Blog et publications" comme dans cette réponse ):

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

Cela signifie que vous n'extrayez que quelques milliers d'entiers en mémoire, par opposition à des milliers de graphiques d'objets entiers, ce qui devrait minimiser l'utilisation de la mémoire tout en vous permettant de travailler élément par élément sans activer MARS.

Un autre avantage intéressant de cet exemple, comme le montre l'exemple, est que vous pouvez enregistrer les modifications lorsque vous parcourez chaque élément au lieu d'attendre la fin de la boucle (ou une autre solution de ce type), comme cela est nécessaire même avec MARS activé (voir ici et ici ).

2
Paul

2 solutions pour atténuer ce problème:

  1. Forcer la mise en cache de la mémoire en conservant le chargement paresseux avec .ToList() après votre requête Afin que vous puissiez ensuite effectuer une itération en ouvrant un nouveau DataReader.
  2. .Include (/ entités supplémentaires que vous souhaitez charger dans la requête /) ceciest appelé un chargement rapide, ce qui vous permet d'inclure (bien) des objets associés (entités) lors de l'exécution d'une requête avec le DataReader.
0
Stefano Beltrame

Dans mon cas, j'ai constaté qu'il manquait des instructions "wait" avant les appels myContext.SaveChangesAsync (). L'ajout d'une attente avant que ces appels asynchrones ne résolve pour moi les problèmes liés au lecteur de données.

0
Elijah Lofgren

Dans ma situation, le problème est dû à un enregistrement d’injection de dépendance. J'injectais un service d'étendue par demande qui utilisait un dbcontext dans un service enregistré singleton. Par conséquent, dbcontext a été utilisé dans plusieurs demandes et par conséquent, l'erreur.

0
E. Staal

J'ai constaté que j'avais la même erreur et cela s'est produit lorsque j'utilisais un Func<TEntity, bool> au lieu d'un Expression<Func<TEntity, bool>> pour votre predicate.

Une fois que j'ai changé tous les Func's en Expression's, l'exception a cessé d'être levée.

Je crois que EntityFramwork fait des choses intelligentes avec Expression's qu’il ne fait tout simplement pas avec Func's

0
sQuir3l

Ce problème peut être résolu simplement en convertissant les données en liste.

 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;
            }
0
Debendra Dash

Si nous essayons de grouper une partie de nos conditions dans une méthode Func <> ou une extension, nous obtiendrons cette erreur, supposons que nous ayons un code comme celui-ci:

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

Cela jettera l'exception si nous essayons de l'utiliser dans un Where (), nous devrions plutôt construire un prédicat comme ceci:

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

Vous pouvez en savoir plus sur: http://www.albahari.com/nutshell/predicatebuilder.aspx

0
Arvand