Entity Framework: ya hay un DataReader abierto asociado con este comando

285

Estoy usando Entity Framework y ocasionalmente obtendré este error.

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

Aunque no estoy haciendo ninguna gestión de conexión manual.

Este error ocurre de forma intermitente.

código que desencadena el error (acortado para facilitar la lectura):

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

usando el patrón Dispose para abrir una nueva conexión cada vez.

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

}

sigue siendo problemático

¿Por qué EF no reutilizaría una conexión si ya está abierta?

Alma sónica
fuente
1
Soy consciente de que esta pregunta es antiguo, pero estaría interesado en saber qué tipo tus predicatey historicPredicatevariables son. He descubierto que si pasas Func<T, bool>a Where()él se compilará y a veces funcionará (porque hace el "dónde" en la memoria). Lo que deberías estar haciendo es pasar Expression<Func<T, bool>>a Where().
James

Respuestas:

351

No se trata de cerrar la conexión. EF gestiona la conexión correctamente. Comprendo que este problema es que hay múltiples comandos de recuperación de datos ejecutados en una sola conexión (o un solo comando con múltiples selecciones) mientras que el siguiente DataReader se ejecuta antes de que el primero haya completado la lectura. La única forma de evitar la excepción es permitir múltiples DataReaders anidados = activar MultipleActiveResultSets. Otro escenario cuando esto sucede siempre es cuando itera a través del resultado de la consulta (IQueryable) y activará una carga diferida para la entidad cargada dentro de la iteración.

Ladislav Mrnka
fuente
2
Eso tendría sentido. pero solo hay una selección dentro de cada método.
Sonic Soul
1
@Sonic: Esa es la pregunta. Tal vez hay más de un comando ejecutado pero no lo ves. No estoy seguro de si esto se puede rastrear en Profiler (se puede lanzar una excepción antes de ejecutar el segundo lector). También puede intentar enviar la consulta a ObjectQuery y llamar a ToTraceString para ver el comando SQL. Es difícil de rastrear. Siempre enciendo MARS.
Ladislav Mrnka 01 de
2
@Sonic: No, mi intención era verificar los comandos SQL ejecutados y completados.
Ladislav Mrnka 01 de
11
genial, mi problema fue el segundo escenario: 'cuando itera a través del resultado de la consulta (IQueryable) y activará una carga diferida para la entidad cargada dentro de la iteración'.
Amr Elgarhy
66
Habilitar MARS aparentemente puede tener efectos secundarios negativos: designlimbo.com/?p=235
Søren Boisen
126

Como alternativa al uso de MARS (MultipleActiveResultSets) puede escribir su código para no abrir múltiples conjuntos de resultados.

Lo que puede hacer es recuperar los datos en la memoria, de esa manera no tendrá el lector abierto. A menudo se debe a la iteración a través de un conjunto de resultados al intentar abrir otro conjunto de resultados.

Código de muestra:

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

Digamos que está haciendo una búsqueda en su base de datos que contiene estos:

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

Podemos hacer una solución simple a esto agregando .ToList () de esta manera:

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

Esto obliga al marco de la entidad a cargar la lista en la memoria, por lo tanto, cuando lo iteramos en el bucle foreach ya no está usando el lector de datos para abrir la lista, sino que está en la memoria.

Me doy cuenta de que esto podría no ser deseable si desea cargar algunas propiedades de forma diferida, por ejemplo. Este es principalmente un ejemplo que, con suerte, explica cómo / por qué podría tener este problema, para que pueda tomar decisiones en consecuencia

Jim Wolff
fuente
77
Esta solución funcionó para mí. Agregue .ToList () justo después de consultar y antes de hacer cualquier otra cosa con el resultado.
TJKjaer
9
Tenga cuidado con esto y use el sentido común. Si ToListusa mil objetos, aumentará la memoria por tonelada. En este ejemplo específico, sería mejor combinar la consulta interna con la primera para que solo se genere una consulta en lugar de dos.
kamranicus
44
@subkamran Mi punto era exactamente eso, pensar en algo y elegir lo que es correcto para la situación, no solo hacer. El ejemplo es algo aleatorio que pensé en explicar :)
Jim Wolff
3
Definitivamente, solo quería señalarlo explícitamente para personas felices de copiar / pegar :)
kamranicus
No me disparen, pero esto no es una solución a la pregunta. ¿Desde cuándo "extraer datos en la memoria" es una solución para un problema relacionado con SQL? Me gusta conversar con la base de datos, así que de ninguna manera preferiría extraer algo de la memoria "porque de lo contrario se produce una excepción SQL". Sin embargo, en su código provisto, no hay razón para contactar la base de datos dos veces. Fácil de hacer en una sola llamada. Ten cuidado con publicaciones como esta. ToList, First, Single, ... Solo debe usarse cuando los datos se necesitan en la memoria (por lo tanto, solo los datos que DESEA), no cuando se produce una excepción SQL de lo contrario.
Frederik Prijck
70

Hay otra forma de superar este problema. Si es una mejor manera depende de su situación.

El problema resulta de la carga diferida, por lo que una forma de evitarlo es no tener una carga diferida, mediante el uso de Incluir:

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

Si usa los Includes apropiados , puede evitar habilitar MARS. Pero si pierde uno, obtendrá el error, por lo que habilitar MARS es probablemente la forma más fácil de solucionarlo.

Ryan Lundy
fuente
1
Trabajado como un encanto. .Includees una solución mucho mejor que habilitar MARS, y mucho más fácil que escribir su propio código de consulta SQL.
Nolonar
15
Si alguien tiene el problema de que solo puede escribir .Include ("string") no una lambda, debe agregar "using System.Data.Entity" porque el método de extensión se encuentra allí.
Jim Wolff
46

Obtiene este error, cuando la colección que está intentando iterar es una carga lenta (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

La conversión de la colección IQueriable en otra colección enumerable resolverá este problema. ejemplo

_dbContext.Users.ToList()

Nota: .ToList () crea un nuevo conjunto cada vez y puede causar problemas de rendimiento si se trata de datos de gran tamaño.

Nalan Madheswaran
fuente
1
¡La solución más fácil posible! Big UP;)
Jacob Sobus
1
¡Obtener listas ilimitadas puede causar graves problemas de rendimiento! ¿Cómo puede alguien votar eso?
SandRock
1
@SandRock no es para alguien que trabaja para una pequeña empresa - SELECT COUNT(*) FROM Users= 5
Simon_Weaver
55
Piénsalo dos veces. Un desarrollador joven que lee este Q / A puede pensar que esta es una solución de todos los tiempos cuando no lo es. Le sugiero que edite su respuesta para advertir a los lectores sobre el peligro de obtener listas ilimitadas de db.
SandRock
1
@SandRock Creo que este sería un buen lugar para vincular una respuesta o un artículo que describa las mejores prácticas.
Sinjai
13

Resolví el problema fácilmente (pragmático) agregando la opción al constructor. Por lo tanto, lo uso solo cuando es necesario.

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...
Harvey Triana
fuente
2
Gracias. Esta funcionando. Acabo de agregar MultipleActiveResultSets = true en la cadena de conexión directamente en web.config
Mosharaf Hossain
11

Intente en su cadena de conexión para establecer MultipleActiveResultSets=true. Esto permite la multitarea en la base de datos.

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

Eso funciona para mí ... ya sea que su conexión en app.config o la configuró mediante programación ... espero que esto sea útil

Mohamed Hocine
fuente
MultipleActiveResultSets = true agregado a su cadena de conexión probablemente resolverá el problema. Esto no debería haber sido rechazado.
Aaron Hudon el
Sí, seguro que he demostrado cómo agregar a su cadena de conexión
Mohamed Hocine
4

Originalmente había decidido usar un campo estático en mi clase API para hacer referencia a una instancia del objeto MyDataContext (donde MyDataContext es un objeto de contexto EF5), pero eso es lo que parece crear el problema. Agregué código como el siguiente a cada uno de mis métodos API y eso solucionó el problema.

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

Como han dicho otras personas, los objetos de contexto de datos de EF NO son seguros para subprocesos. Por lo tanto, colocarlos en el objeto estático eventualmente causará el error "lector de datos" en las condiciones adecuadas.

Mi suposición original era que crear solo una instancia del objeto sería más eficiente y permitiría una mejor gestión de la memoria. Por lo que he reunido investigando este tema, ese no es el caso. De hecho, parece ser más eficiente tratar cada llamada a su API como un evento aislado y seguro para subprocesos. Asegurarse de que todos los recursos se liberen correctamente, ya que el objeto queda fuera de alcance.

Esto tiene sentido especialmente si lleva su API a la siguiente progresión natural, que sería exponerla como un WebService o REST API.

Divulgar

  • SO: Windows Server 2012
  • .NET: instalado 4.5, proyecto usando 4.0
  • Fuente de datos: MySQL
  • Marco de aplicación: MVC3
  • Autenticación: formularios
Jeffrey A. Gochin
fuente
3

Noté que este error ocurre cuando envío un IQueriable a la vista y lo uso en un foreach doble, donde el foreach interno también necesita usar la conexión. Ejemplo simple (ViewBag.parents puede ser IQueriable o DbSet):

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

    }
}

La solución simple es usar .ToList()en la colección antes de usarla. También tenga en cuenta que MARS no funciona con MySQL.

cen
fuente
¡GRACIAS! Todo lo que dice aquí dice "el problema es bucles anidados", pero nadie dijo cómo solucionarlo. Puse un ToList()en mi primera llamada para obtener una colección de la base de datos. Luego hice un foreachen esa lista y las llamadas posteriores funcionaron perfectamente en lugar de dar el error.
AlbatrossCafe
@AlbatrossCafe ... pero nadie menciona que en ese caso sus datos se cargarán en la memoria y la consulta se ejecutará en la memoria, en lugar de DB
Lightning3
3

Descubrí que tenía el mismo error, y ocurrió cuando estaba usando un en Func<TEntity, bool>lugar de un Expression<Func<TEntity, bool>>para su predicate.

Una vez que cambié todo Func'sa Expression'sla excepción, dejé de ser arrojado.

Creo que EntityFramworkhace algunas cosas inteligentes con Expression'slas que simplemente no haceFunc's

sQuir3l
fuente
Esto necesita más votos a favor. Estaba tratando de diseñar un método en mi clase DataContext tomando un (MyTParent model, Func<MyTChildren, bool> func)modo para que mis ViewModels pudieran especificar una cierta wherecláusula para el método Generic DataContext. Nada funcionaba hasta que hice esto.
Justin
3

2 soluciones para mitigar este problema:

  1. Fuerce el almacenamiento en caché de memoria manteniendo la carga lenta .ToList() después de su consulta, para que pueda iterar abriendo un nuevo DataReader.
  2. .Include(/ entidades adicionales que desea cargar en la consulta /) esto se llama carga ansiosa, que le permite (de hecho) incluir objetos asociados (entidades) durante la ejecución de una consulta con el DataReader.
Stefano Beltrame
fuente
2

Un buen punto intermedio entre habilitar MARS y recuperar todo el conjunto de resultados en la memoria es recuperar solo ID en una consulta inicial y luego recorrer las ID que materializan cada entidad a medida que avanza.

Por ejemplo (usando las entidades de muestra "Blog y publicaciones" como en esta respuesta ):

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

Hacer esto significa que solo extrae unos pocos miles de enteros en la memoria, en lugar de miles de gráficos de objetos completos, lo que debería minimizar el uso de la memoria al tiempo que le permite trabajar elemento por elemento sin habilitar MARS.

Otro buen beneficio de esto, como se ve en la muestra, es que puede guardar los cambios a medida que recorre cada elemento, en lugar de tener que esperar hasta el final del ciclo (o alguna otra solución similar), como sería necesario incluso con MARS habilitado (ver aquí y aquí ).

Pablo
fuente
context.SaveChanges();inside loop :(. Esto no es bueno. Debe estar fuera del loop.
Jawand Singh
1

En mi caso, descubrí que faltaban declaraciones de "espera" antes de las llamadas myContext.SaveChangesAsync (). Agregar esperar antes de esas llamadas asíncronas solucionó los problemas del lector de datos para mí.

Elijah Lofgren
fuente
0

Si intentamos agrupar parte de nuestras condiciones en un Func <> o método de extensión, obtendremos este error, supongamos que tenemos un código como este:

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

Esto arrojará la excepción si intentamos usarlo en un Where (), lo que deberíamos hacer en su lugar es construir un predicado como este:

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

Se puede leer más en: http://www.albahari.com/nutshell/predicatebuilder.aspx

Arvand
fuente
0

Este problema se puede resolver simplemente convirtiendo los datos en una lista

 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
fuente
ToList () realiza la llamada pero el código anterior aún no elimina la conexión. por lo que su _webcontext todavía está en riesgo de ser cerrado en el momento de la línea 1
Sonic Soul
0

En mi situación, el problema ocurrió debido a un registro de inyección de dependencia. Estaba inyectando un servicio de alcance por solicitud que estaba usando un dbcontext en un servicio registrado singleton. Por lo tanto, el dbcontext se utilizó dentro de múltiples solicitudes y, por lo tanto, el error.

E. Staal
fuente
0

En mi caso, el problema no tenía nada que ver con la cadena de conexión MARS sino con la serialización json. Después de actualizar mi proyecto de NetCore2 a 3, recibí este error.

Más información se puede encontrar aquí

Si no
fuente
-6

Resolví este problema usando la siguiente sección de código antes de la segunda consulta:

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

puedes cambiar el tiempo de sueño en milisegundos

PD útil cuando se usan hilos

i31nGo
fuente
13
Agregar arbitrariamente Thread.Sleep en cualquier solución es una mala práctica, y es especialmente malo cuando se usa para evitar un problema diferente donde el estado de algún valor no se entiende completamente. Pensé que "Usar subprocesos" como se indica al final de la respuesta significaría tener al menos una comprensión básica de subprocesos, pero esta respuesta no tiene en cuenta ningún contexto, especialmente aquellas circunstancias en las que es una muy mala idea usar Thread.Sleep, como en un subproceso de interfaz de usuario.
Mike Tours