linq - practices - entity framework precompiled query
Entity Framework: ya existe un DataReader abierto asociado con este comando (14)
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 intermitentemente
Código que activa 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?
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 es causada por 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 la siguiente manera:
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();
Esto obliga a que el marco de la entidad cargue la lista en la memoria, por lo tanto, cuando iteramos en el bucle foreach, ya no está utilizando 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 quiere descargar algunas propiedades, 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.
Descubrí que tenía el mismo error, y ocurrió cuando estaba usando Func<TEntity, bool>
lugar de Expression<Func<TEntity, bool>>
para su predicate
.
Una vez que cambié todos los Func''s
a Expression''s
la excepción dejó de ser lanzada.
Creo que EntityFramwork
hace algunas cosas inteligentes con Expression''s
que simplemente no hace con Func''s
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 usaba un dbcontext en un servicio registrado de singleton. Por lo tanto, se usó el dbcontext dentro de la solicitud múltiple y por lo tanto el error.
Este problema se puede resolver simplemente convirtiendo los datos a 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;
}
Hay otra manera de superar este problema. Si es una forma mejor depende de su situación.
El problema se debe a la carga lenta, por lo que una forma de evitarlo es no tener una carga lenta mediante el uso de Incluir:
var results = myContext.Customers
.Include(x => x.Orders)
.Include(x => x.Addresses)
.Include(x => x.PaymentMethods);
Si utiliza las Include
s apropiadas, 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.
No se trata de cerrar la conexión. EF gestiona la conexión correctamente. Mi comprensión de 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 se itera a través del resultado de la consulta (IQueryable) y se activará la carga perezosa para la entidad cargada dentro de la iteración.
Noté que este error ocurre cuando envío un IQueriable a la vista y lo uso en un doble foreach, 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.
Obtiene este error, cuando la recopilación que está intentando iterar es de tipo perezoso de carga (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 conjunto nuevo cada vez que puede causar problemas de rendimiento si está tratando con datos grandes.
Originalmente había decidido usar un campo estático en mi clase de API para hacer referencia a una instancia del objeto MyDataContext (donde MyDataContext es un objeto de contexto EF5), pero eso es lo que parecía crear el problema. Agregué un código similar al 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 indicado otras personas, los objetos del contexto de datos de EF NO son seguros para subprocesos. Por lo tanto, colocarlos en el objeto estático eventualmente causará el error de "lector de datos" en las condiciones correctas.
Mi suposición original era que crear solo una instancia del objeto sería más eficiente y permitiría una mejor administración de la memoria. De 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 del 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.
Revelación
- SO: Windows Server 2012
- .NET: Instalado 4.5, Proyecto usando 4.0
- Fuente de datos: MySQL
- Marco de aplicación: MVC3
- Autenticación: Formularios
Resolví el problema fácilmente (pragmático) agregando la opción al constructor. Por lo tanto, uso eso 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;" : "");
}
...
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 usas hilos
Si intentamos agrupar parte de nuestras condiciones en un Func <o un 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 lanzará 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);
}
Más información se puede leer en: http://www.albahari.com/nutshell/predicatebuilder.aspx
Un buen punto medio entre habilitar MARS y recuperar todo el conjunto de resultados en la memoria es recuperar solo las ID en una consulta inicial y luego recorrer las ID que materializan cada entidad a medida que avanza.
Por ejemplo (utilizando las entidades de muestra "Blog and Posts" 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 mientras le permite trabajar elemento por elemento sin habilitar MARS.
Otra buena ventaja 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 bucle (o alguna otra solución similar), como sería necesario incluso con MARS habilitado (ver here y here ).
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 me funciona ... ya sea que tu conexión en app.config o la configures programáticamente ... espero que esto servicial