Entity Framework: Já existe um DataReader aberto associado a este comando

Estou usando o Entity Framework e, ocasionalmente, recebo esse erro.

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

Mesmo que eu não esteja fazendo nenhum gerenciamento de conexão manual.

esse erro acontece intermitentemente.

código que aciona o erro (abreviado para facilitar a leitura):

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

usando o padrão Dispose para abrir novas conexões todas as vezes.

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

ainda problemático

por que o EF não reutilizaria uma conexão se ela já estivesse aberta?

Não é sobre fechar a conexão. O EF gerencia a conexão corretamente. Meu entendimento deste problema é que existem múltiplos comandos de recuperação de dados executados em uma única conexão (ou comando único com múltiplas seleções) enquanto o próximo DataReader é executado antes que a primeira tenha concluído a leitura. A única maneira de evitar a exceção é permitir que vários DataReaders nesteds = ativem MultipleActiveResultSets. Outro cenário, quando isso sempre acontece, é quando você percorre o resultado da consulta (IQueryable) e acionará o carregamento lento para a entidade carregada dentro da iteração.

Como alternativa ao uso de MARS (MultipleActiveResultSets), você pode escrever seu código para que você não abra vários conjuntos de resultados.

O que você pode fazer é recuperar os dados na memory, dessa forma você não terá o leitor aberto. Isso geralmente é causado pela iteração por meio de um conjunto de resultados ao tentar abrir outro conjunto de resultados.

Código de amostra:

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

Vamos dizer que você está fazendo uma pesquisa no seu database contendo estes:

 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 fazer uma solução simples para isso adicionando .ToList () assim:

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

Isso força o entityframework a carregar a lista na memory, assim, quando a iteramos no loop foreach, ela não está mais usando o leitor de dados para abrir a lista, ela está na memory.

Eu percebo que isso pode não ser desejado se você quiser preguear algumas propriedades, por exemplo. Este é principalmente um exemplo que explica como / por que você pode obter esse problema, então você pode tomar decisões de acordo

Existe outra maneira de superar esse problema. Se é uma maneira melhor depende da sua situação.

O problema resulta do carregamento lento, portanto, uma maneira de evitar isso é não ter carregamento lento, por meio do uso de Incluir:

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

Se você usar os Include s apropriados, poderá evitar a ativação do MARS. Mas se você perder um, você receberá o erro, então habilitar o MARS é provavelmente a maneira mais fácil de consertá-lo.

Você recebe este erro, quando a coleção que você está tentando iterar é um tipo de carregamento lento (IQueriable).

 foreach (var user in _dbContext.Users) { } 

Converter a coleção IQueriable em outra coleção enumerável resolverá esse problema. exemplo

 _dbContext.Users.ToList() 

Nota: .ToList () cria um novo conjunto de cada vez e pode causar o problema de desempenho se você estiver lidando com dados grandes.

Eu resolvi o problema facilmente (pragmático) adicionando a opção ao construtor. Assim, eu uso isso somente quando necessário.

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

tente na sua cadeia de conexão para definir “MultipleActiveResultSets = true”, isso permite multitarefa no database. “Servidor = yourserver; AttachDbFilename = database; User Id = sa; senha = blah; MultipleActiveResultSets = true; App = EntityFramework” isso funciona para mim … se sua conexão em app.config ou você configurá-lo programaticamente … espero que isso útil

Eu tinha originalmente decidido usar um campo estático na minha class de API para fazer referência a uma instância do object MyDataContext (onde MyDataContext é um object de contexto EF5), mas isso é o que parecia criar o problema. Eu adicionei algo como o seguinte a cada um dos meus methods de API e isso resolveu o problema.

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

Como outras pessoas afirmaram, os objects do contexto de dados da EF não são thread-safe. Portanto, colocá-los no object estático acabará causando o erro “leitor de dados” nas condições corretas.

Minha suposição original era que criar apenas uma instância do object seria mais eficiente e proporcionaria melhor gerenciamento de memory. Pelo que eu recolhi pesquisando essa questão, esse não é o caso. Na verdade, parece ser mais eficiente tratar cada chamada para sua API como um evento isolado e seguro para encadeamentos. Garantir que todos os resources sejam liberados adequadamente, pois o object sai do escopo.

Isso faz sentido, especialmente se você levar sua API para a próxima progressão natural, que seria expô-la como uma API WebService ou REST.

Divulgação

  • SO: Windows Server 2012
  • .NET: Instalado 4.5, Projeto usando 4.0
  • Fonte de dados: MySQL
  • Estrutura de Aplicação: MVC3
  • Autenticação: Formulários

Notei que esse erro acontece quando eu envio uma IQueriable para a view e a utilizo em um foreach duplo, onde o foreach interno também precisa usar a conexão. Exemplo simples (ViewBag.parents pode ser IQueriable ou DbSet):

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

A solução simples é usar .ToList() na coleção antes de usá-lo. Observe também que o MARS não funciona com o MySQL.

Um bom meio-termo entre habilitar o MARS e recuperar todo o conjunto de resultados na memory é recuperar apenas IDs em uma consulta inicial e, em seguida, percorrer os IDs que materializam cada entidade à medida que você avança.

Por exemplo (usando as entidades de amostra “Blog e Posts” como nesta resposta ):

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

Fazer isso significa que você só extrai alguns milhares de inteiros na memory, ao contrário de milhares de charts de object inteiros, o que deve minimizar o uso de memory enquanto permite que você trabalhe item por item sem habilitar o MARS.

Outro benefício interessante disso, como visto na amostra, é que você pode salvar as alterações à medida que você passa por cada item, em vez de ter que esperar até o final do loop (ou outra solução alternativa), como seria necessário mesmo com MARS ativado (veja aqui e aqui ).

Descobri que tive o mesmo erro, e ocorreu quando eu estava usando um Func vez de uma Expression> para o seu predicate .

Uma vez que eu mudei todos os Func's para Expression's a exceção parou de ser lançada.

Eu acredito que o EntityFramwork faz algumas coisas inteligentes com o Expression's que ele simplesmente não faz com o Func's

Se tentarmos agrupar parte de nossas condições em um método Func <> ou de extensão, obteremos esse erro, suponha que tenhamos um código como este:

 public static Func IsCurrent() { return p => (p.ValidFrom == null || p.ValidFrom < = DateTime.Now) && (p.ValidTo == null || p.ValidTo >= DateTime.Now); } Or public static IEnumerable IsCurrent(this IEnumerable prices) { .... } 

Isso lançará a exceção se tentarmos usá-lo em um Where (), o que devemos fazer é construir um predicado assim:

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

Ainda mais pode ser lido em: http://www.albahari.com/nutshell/predicatebuilder.aspx

Este problema pode ser resolvido simplesmente convertendo os dados para uma 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; } 

Na minha situação, o problema ocorreu devido a um registro de injeção de dependência. Eu estava injetando um serviço de escopo por solicitação que estava usando um dbcontext em um serviço registrado singleton. Portanto, o dbcontext foi usado em várias solicitações e, portanto, o erro.

Eu resolvi esse problema usando a seguinte seção de código antes da segunda consulta:

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

você pode mudar o tempo de sono em milissegundos

PD Útil ao usar threads