Consulta do Entity Framework lenta, mas o mesmo SQL em SqlQuery é rápido

93

Estou vendo um desempenho realmente estranho relacionado a uma consulta muito simples usando o Entity Framework Code-First com .NET framework versão 4. A consulta LINQ2Entities se parece com isto:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

Isso leva mais de 3.000 milissegundos para ser executado. O SQL gerado parece muito simples:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

Essa consulta é executada quase instantaneamente quando executada por meio do Management Studio. Quando eu altero o código C # para usar a função SqlQuery, ele é executado em 5 a 10 milissegundos:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

Portanto, exatamente o mesmo SQL, as entidades resultantes são rastreadas por alterações em ambos os casos, mas uma grande diferença de desempenho entre os dois. O que da?

Brian Sullivan
fonte
2
Espero que você esteja vendo atrasos na inicialização - provavelmente, veja a compilação. Consulte MSDN:Performance Considerations for Entity Framework 5
Nicholas Butler,
Eu tentei pré-gerar visualizações e não parece ajudar. Além disso, executei outra consulta EF antes da lenta para descartar o material de inicialização. A nova consulta foi executada rapidamente, a lenta ainda funcionou lentamente, embora o aquecimento do contexto tenha ocorrido durante a primeira consulta.
Brian Sullivan,
1
@marc_s - Não, SqlQuery retornará uma instância de entidade totalmente materializada e com controle de alterações. Consulte msdn.microsoft.com/en-us/library/…
Brian Sullivan
O SQL gerado para sua consulta EF está realmente embutindo o valor do parâmetro ou usando um parâmetro? Isso não deve afetar a velocidade de consulta para uma consulta individual, mas pode causar o inchaço do plano de consulta no servidor ao longo do tempo.
Jim Wooley,
Você já tentou executar a mesma consulta duas vezes / várias vezes? Quanto tempo demorou ao executar pela segunda vez? Você já tentou fazer isso no .NET Framework 4.5 - existem algumas melhorias de desempenho relacionadas ao EF no .NET Framework 4.5 que podem ajudar.
Pawel,

Respostas:

96

Encontrei. Acontece que é um problema de tipos de dados SQL. A SomeStringPropcoluna no banco de dados era um varchar, mas EF assume que os tipos de string .NET são nvarchars. O processo de tradução resultante durante a consulta para o banco de dados fazer a comparação é o que leva muito tempo. Acho que o EF Prof estava me enganando um pouco aqui, uma representação mais precisa da consulta que está sendo executada seria a seguinte:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

Portanto, a correção resultante é anotar o modelo de código, indicando o tipo de dados SQL correto:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}
Brian Sullivan
fonte
1
Boa investigação. Sua consulta estava sofrendo de "conversão implícita", conforme explicado aqui: brentozar.com/archive/2012/07/…
Jaime
Me salvou algumas horas de depuração. Esse era exatamente o problema.
Cody
1
No meu caso, estou usando o EDMX com um banco de dados legado, que usa varcharpara tudo, e de fato esse era o problema. Gostaria de saber se posso fazer um EDMX para considerar varchar para tudo coluna de string.
Alisson
1
Ótima descoberta, homem. mas @Jaime o que devemos fazer para primeiro enfoque o banco de dados, pois tudo (por exemplo, anotação de dados em modelos db) desaparece após atualizar o modelo EF do banco de dados.
Nauman Khan
Definindo isso como minha página inicial por um tempo para que eu possa reviver a emoção de encontrar uma resposta tão boa por um tempo. Obrigado!!!
OJisBad
43

O motivo de desacelerar minhas consultas feitas no EF foi comparar escalares não anuláveis ​​com escalares anuláveis:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

Essa consulta demorou 35 segundos. Mas uma pequena refatoração como essa:

long? userId = 10;
long userIdValue = userId.Value; // I've done that only for the presentation pursposes

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

dá resultados incríveis. Demorou apenas 50 ms para ser concluído. É possível que seja um bug no EF.

chora
fonte
13
Isso é tão estranho
Daniel Cardenas
1
AMD. Aparentemente, isso também pode acontecer quando o uso de interfaces IUserId.Id estava causando o problema, mas primeiro o mapeamento de Id para um número inteiro funciona ... devo verificar agora todas as consultas em meu aplicativo de 100.000 linhas?
Dirk Boer
este bug foi relatado? Ainda está na última versão 6.2.0
Dirk Boer
2
O mesmo problema também está no EF Core. Obrigado por encontrar isso!
Yannickv
Outra sugestão é processar a variável antes de colocá-la na expressão do LINQ. Caso contrário, o sql gerado será muito mais longo e lento. Eu experimentei ter Trim () e ToLower () dentro da expressão do LINQ que me incomoda.
samheihey
4

Tive o mesmo problema (a consulta é rápida quando executada do gerenciador de SQL) mas quando executada do EF o tempo limite expira.

Acontece que a entidade (que foi criada a partir da visualização) tinha chaves de entidade erradas. Portanto, a entidade tinha linhas duplicadas com as mesmas chaves e acho que tinha que fazer o agrupamento no fundo.

Vladimir Gedgafov
fonte
3

Eu também me deparei com isso com uma consulta ef complexa. Uma correção para mim que reduziu uma consulta ef de 6 segundos para a consulta sql sub-segundo que gerou foi desligar o carregamento lento.

Para encontrar esta configuração (ef 6) vá para o arquivo .edmx e procure em Propriedades -> Geração de código -> Carregamento lento ativado. Defina como falso.

Melhoria maciça de desempenho para mim.

user2622095
fonte
4
Isso é legal, mas não tem nada a ver com a questão dos pôsteres.
Jace Rhea,
2

Eu tive esse problema também. Acontece que o culpado no meu caso foi a detecção de parâmetros do SQL Server .

A primeira pista de que meu problema era na verdade devido à detecção de parâmetro era que executar a consulta com "set arithabort off" ou "set arithabort on" gerava tempos de execução drasticamente diferentes no Management Studio. Isso ocorre porque o ADO.NET por padrão usa "set arithabort off" e o Management Studio usa como padrão "set arithabort on". O cache do plano de consulta mantém planos diferentes dependendo deste parâmetro.

Desativei o cache do plano de consulta para a consulta, com a solução que você encontra aqui .

Oskar Sjöberg
fonte