Estrutura de entidades: já existe um DataReader aberto associado a este comando

285

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

usando Dispose pattern para abrir sempre uma nova conexão.

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

}

ainda problemático

por que a EF não reutilizaria uma conexão se ela já estiver aberta.

Sonic Soul
fonte
1
Eu percebo que essa pergunta é antiga, mas eu estaria interessado em saber que tipo são suas predicatee historicPredicatevariáveis. Eu descobri que se você passar Func<T, bool>para Where()ele irá compilar e, às vezes, funcionar (porque faz o "onde" na memória). O que você deve fazer é passar Expression<Func<T, bool>>para Where().
James

Respostas:

351

Não se trata de fechar a conexão. A EF gerencia a conexão corretamente. Meu entendimento desse problema é que existem vários comandos de recuperação de dados executados em conexão única (ou comando único com várias seleções) enquanto o próximo DataReader é executado antes que o primeiro conclua a leitura. A única maneira de evitar a exceção é permitir vários DataReaders aninhados = ativar MultipleActiveResultSets. Outro cenário em que isso sempre acontece é quando você itera pelo resultado da consulta (IQueryable) e aciona o carregamento lento da entidade carregada na iteração.

Ladislav Mrnka
fonte
2
isso faria sentido. mas há apenas uma seleção em cada método.
Sonic Soul
1
@ Sonic: Essa é a questão. Talvez exista mais de um comando executado, mas você não o vê. Não tenho certeza se isso pode ser rastreado no Profiler (exceção pode ser lançada antes da execução do segundo leitor). Você também pode tentar converter a consulta no ObjectQuery e chamar ToTraceString para ver o comando SQL. É difícil de rastrear. Eu sempre ligo o MARS.
Ladislav Mrnka
2
@ Sonic: Não, minha intenção era verificar comandos SQL executados e concluídos.
Ladislav Mrnka
11
ótimo, meu problema foi o segundo cenário: 'quando você itera pelo resultado da consulta (IQueryable) e aciona o carregamento lento da entidade carregada dentro da iteração'.
Amr Elgarhy 13/09/11
6
MARS permitindo que pode , aparentemente, tem maus efeitos colaterais: designlimbo.com/?p=235
Søren Boisen
126

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

O que você pode fazer é recuperar os dados na memória, para que você não abra o leitor. Isso geralmente é causado pela iteração em um conjunto de resultados ao tentar abrir outro conjunto de resultados.

Código de amostra:

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

Vamos dizer que você está fazendo uma pesquisa em seu banco de dados 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 framework de entidade a carregar a lista na memória; portanto, quando iteramos no loop foreach, ele não está mais usando o leitor de dados para abrir a lista, mas sim na memória.

Sei que isso pode não ser o desejado se você deseja carregar preguiçosamente algumas propriedades, por exemplo. Este é principalmente um exemplo que, com sorte, explica como / por que você pode ter esse problema, para que possa tomar decisões de acordo.

Jim Wolff
fonte
7
Esta solução funcionou para mim. Adicione .ToList () logo após consultar e antes de fazer qualquer outra coisa com o resultado.
TJKjaer
9
Tenha cuidado com isso e use o bom senso. Se você estiver ToListusando mil objetos, isso aumentará a memória em uma tonelada. Neste exemplo específico, seria melhor combinar a consulta interna com a primeira, para que apenas uma consulta seja gerada em vez de duas.
kamranicus
4
@subkamran Meu argumento era exatamente isso, pensando em algo e escolhendo o que é certo para a situação, não apenas fazendo. O exemplo é apenas algo que eu aleatório pensado para explicar :)
Jim Wolff
3
Definitivamente, eu só queria indicá-lo explicitamente para copy / paste-happy pessoas :)
kamranicus
Não atire em mim, mas isso não é de forma alguma uma solução para a questão. Desde quando "puxar dados na memória" é uma solução para um problema relacionado ao SQL? Eu gosto de estar conversando com o banco de dados, então, de maneira alguma, eu preferiria extrair algo da memória "porque, caso contrário, uma exceção SQL será lançada". No entanto, no código fornecido, não há motivo para entrar em contato com o banco de dados duas vezes. Fácil de fazer em uma chamada. Tenha cuidado com posts como este. ToList, First, Single, ... Só deve ser usado quando os dados são necessários na memória (apenas os dados que você QUER), não quando uma exceção SQL estiver ocorrendo de outra forma.
Frederik Prijck
70

Há 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 evitá-lo é não ter um carregamento lento, através do uso de Incluir:

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

Se você usar os Includes apropriados , poderá evitar ativar o MARS. Mas se você perder um, receberá o erro, portanto, habilitar o MARS é provavelmente a maneira mais fácil de corrigi-lo.

Ryan Lundy
fonte
1
Funcionou como um encanto. .Includeé uma solução muito melhor do que habilitar o MARS e muito mais fácil do que escrever seu próprio código de consulta SQL.
Nolonar 28/03
15
Se alguém tiver o problema de que você pode gravar apenas o .Include ("string") e não um lambda, adicione "using System.Data.Entity" porque o método de extensão está localizado lá.
Jim Wolff
46

Você recebe esse 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 sempre e pode causar problemas de desempenho se você estiver lidando com grandes dados.

Nalan Madheswaran
fonte
1
A solução mais fácil possível! Big UP;)
Jacob Sobus
1
A busca de listas ilimitadas pode causar problemas graves de desempenho! Como alguém pode votar isso?
SandRock 03/02
1
@SandRock não para alguém que trabalha para uma pequena empresa - SELECT COUNT(*) FROM Users= 5
Simon_Weaver
5
Pense duas vezes sobre isso. Um jovem desenvolvedor que está lendo este Q / A pode pensar que esta é uma solução de todos os tempos, quando absolutamente não é. Sugiro que você edite sua resposta para avisar os leitores sobre o perigo de buscar listas ilimitadas no db.
SandRock
1
@SandRock Acho que seria um bom lugar para você vincular uma resposta ou um artigo descrevendo as melhores práticas.
Sinjai
13

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;" : "");
    }
...
Harvey Triana
fonte
2
Obrigado. Está funcionando. Acabei de adicionar os MultipleActiveResultSets = true na seqüência de conexão diretamente no web.config
Mosharaf Hossain
11

Experimente na sua cadeia de conexão para definir MultipleActiveResultSets=true. Isso permite multitarefa no banco de dados.

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

Isso funciona para mim ... se a sua conexão no app.config ou você a define programaticamente ... espero que seja útil

Mohamed Hocine
fonte
MultipleActiveResultSets = true adicionado à sua cadeia de conexão provavelmente resolverá o problema. Isso não deveria ter sido rejeitado.
Aaron Hudon
Sim, claro que demonstrei como adicionar à sua seqüência de conexão #
Mohamed Hocine 30/10
4

Originalmente, eu decidi usar um campo estático na minha classe API para fazer referência a uma instância do objeto MyDataContext (Onde MyDataContext é um objeto de Contexto EF5), mas foi isso que pareceu criar o problema. Adicionei código parecido com o seguinte a cada um dos meus métodos de API e isso corrigiu o problema.

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

Como outras pessoas declararam, os objetos EF Data Context NÃO são seguros para threads. Portanto, colocá-los no objeto estático acabará causando o erro "leitor de dados" nas condições corretas.

Minha suposição original era que a criação de apenas uma instância do objeto seria mais eficiente e proporcionaria um melhor gerenciamento de memória. Pelo que reuni pesquisando essa questão, esse não é o caso. De fato, parece ser mais eficiente tratar cada chamada de sua API como um evento isolado e seguro de thread. Garantir que todos os recursos sejam liberados corretamente, pois o objeto sai do escopo.

Isso faz sentido, especialmente se você levar sua API para a próxima progressão natural, que seria expor como API de WebService ou REST.

Divulgação

  • SO: Windows Server 2012
  • .NET: Instalado 4.5, Projeto usando 4.0
  • Fonte de dados: MySQL
  • Estrutura de aplicativos: MVC3
  • Autenticação: Formulários
Jeffrey A. Gochin
fonte
3

Percebi que esse erro ocorre quando envio um IQueriable para a exibição e o uso 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()a coleção antes de usá-la. Observe também que o MARS não funciona com o MySQL.

cen
fonte
OBRIGADO! Tudo aqui dizia que "loops aninhados é o problema", mas ninguém disse como corrigi-lo. Eu fiz uma ToList()primeira chamada para obter uma coleção do banco de dados. Então eu fiz uma foreachlista e as chamadas subseqüentes funcionaram perfeitamente em vez de dar o erro.
AlbatrossCafe
@AlbatrossCafe ... mas ninguém menciona que, nesse caso, seus dados serão carregados na memória e consulta será executado na memória, em vez de DB
LIGHTNING3
3

Eu descobri que tinha o mesmo erro e ocorreu quando eu estava usando um em Func<TEntity, bool>vez de um Expression<Func<TEntity, bool>>para o seupredicate .

Depois que mudei tudo Func'sparaExpression's exceção deixou de ser lançada.

Eu acredito que isso EntityFramworkfaz algumas coisas inteligentes com as Expression'squais simplesmente não se relacionaFunc's

sQuir3l
fonte
Isso precisa de mais votos. Eu estava tentando criar um método na minha classe DataContext, usando um método (MyTParent model, Func<MyTChildren, bool> func)para que meus ViewModels pudessem especificar uma determinada wherecláusula para o método Generic DataContext. Nada estava funcionando até eu fazer isso.
Justin
3

2 soluções para mitigar esse problema:

  1. Forçar o armazenamento em cache da memória mantendo um carregamento lento com .ToList() após a consulta, para que você possa iterar através da abertura de um novo DataReader.
  2. .Include(/ entidades adicionais que você deseja carregar na consulta /) isso é chamado de carregamento rápido, o que permite (de fato) incluir objetos (entidades) associados durante a execução de uma consulta com o DataReader.
Stefano Beltrame
fonte
2

Um bom meio termo entre ativar o MARS e recuperar todo o conjunto de resultados na memória é 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 exemplo "Blog e postagens", 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ê apenas puxa alguns milhares de números inteiros para a memória, em oposição a milhares de gráficos de objetos inteiros, o que deve minimizar o uso da memória enquanto permite trabalhar item por item sem ativar o MARS.

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

Paulo
fonte
context.SaveChanges();.. loop dentro :( Isso não é bom, deve ser fora do loop.
Jawand Singh
1

No meu caso, descobri que faltavam instruções "aguardar" antes das chamadas myContext.SaveChangesAsync (). A adição de espera antes dessas chamadas assíncronas resolveu os problemas do leitor de dados para mim.

Elijah Lofgren
fonte
0

Se tentarmos agrupar parte de nossas condições em um método Func <> ou extensão, obteremos esse erro, suponha que tenhamos um 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) { .... }

Isso lançará a exceção se tentarmos usá-lo em um Where (), o que devemos fazer é criar um 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);
}

Mais informações podem ser lidas em: http://www.albahari.com/nutshell/predicatebuilder.aspx

Arvand
fonte
0

Esse problema pode ser resolvido simplesmente convertendo os dados em 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;
            }
Debendra Dash
fonte
o ToList () faz a chamada, mas o código acima ainda não descarta a conexão. para que o seu _webcontext ainda está em risco de ser fechado no momento da linha 1
sonic Alma
0

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.

E. Staal
fonte
0

No meu caso, o problema não tinha nada a ver com a cadeia de conexão MARS, mas com a serialização json. Depois de atualizar meu projeto do NetCore2 para 3, recebi esse erro.

Mais informações podem ser encontradas aqui

Se não
fonte
-6

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 alterar a hora de dormir em milissegundos

PD Útil ao usar threads

i31nGo
fonte
13
A adição arbitrária de Thread.Sleep em qualquer solução é uma prática ruim - e é especialmente ruim quando usada para contornar um problema diferente, onde o estado de algum valor não é totalmente compreendido. Eu teria pensado que "Usando Threads", conforme declarado na parte inferior da resposta, significaria ter pelo menos algum entendimento básico de encadeamento - mas essa resposta não leva em consideração nenhum contexto, especialmente aquelas circunstâncias em que é uma péssima idéia para usar Thread.Sleep - como em um thread da interface do usuário.
Mike Tours