tl;dr Vários
Include
s explodir o conjunto de resultados SQL. Logo fica mais barato carregar dados por várias chamadas de banco de dados em vez de executar uma mega instrução. Tente encontrar a melhor combinação de Include
e Load
declarações.
parece que há uma penalidade de desempenho ao usar Incluir
Isso é um eufemismo! Vários
Include
s rapidamente expandem o resultado da consulta SQL tanto em largura quanto em comprimento. Por que é que? Fator de crescimento de Include
(Esta parte se aplica ao Entity Framework clássico, v6 e anterior)
Digamos que temos
- entidade raiz
Root
- entidade pai
Root.Parent
- entidades filhas
Root.Children1
eRoot.Children2
- uma instrução LINQ
Root.Include("Parent").Include("Children1").Include("Children2")
Isso cria uma instrução SQL que tem a seguinte estrutura:
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
Estes
<PseudoColumns>
consistem em expressões como CAST(NULL AS int) AS [C2],
e servem para ter a mesma quantidade de colunas em todos os UNION
-ed consultas. A primeira parte adiciona pseudo colunas para Child2
, a segunda parte adiciona pseudo colunas para Child1
. Isto é o que significa para o tamanho do conjunto de resultados SQL:
- Número de colunas no
SELECT
cláusula é a soma de todas as colunas nas quatro tabelas - O número de linhas é a soma dos registros nas coleções filhas incluídas
Como o número total de pontos de dados é
columns * rows
, cada Include
adicional aumenta exponencialmente o número total de pontos de dados no conjunto de resultados. Deixe-me demonstrar isso pegando Root
novamente, agora com um Children3
adicional coleção. Se todas as tabelas tiverem 5 colunas e 100 linhas, teremos:Um
Include
(Root
+ 1 coleção filho):10 colunas * 100 linhas =1.000 pontos de dados.Dois
Include
s (Root
+ 2 coleções filhas):15 colunas * 200 linhas =3.000 pontos de dados.Três
Include
s (Root
+ 3 coleções filhas):20 colunas * 300 linhas =6000 pontos de dados. Com 12
Includes
isso equivaleria a 78.000 pontos de dados! Por outro lado, se você obtiver todos os registros para cada tabela separadamente em vez de 12
Includes
, você tem 13 * 5 * 100
pontos de dados:6500, menos de 10%! Agora, esses números são um pouco exagerados, pois muitos desses pontos de dados serão
null
, portanto, eles não contribuem muito para o tamanho real do conjunto de resultados que é enviado ao cliente. Mas o tamanho da consulta e a tarefa do otimizador de consulta certamente são afetados negativamente pelo aumento do número de Include
s. Saldo
Então, usando
Includes
é um equilíbrio delicado entre o custo das chamadas de banco de dados e o volume de dados. É difícil dar uma regra prática, mas agora você pode imaginar que o volume de dados geralmente supera rapidamente o custo de chamadas extras se houver mais de ~3 Includes
para coleções filhas (mas um pouco mais para as coleções pai Includes
, que apenas ampliam o conjunto de resultados). Alternativa
A alternativa para
Include
é carregar dados em consultas separadas:context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
Isso carrega todos os dados necessários no cache do contexto. Durante esse processo, o EF executa correção de relacionamento pelo qual ele preenche automaticamente as propriedades de navegação (
Root.Children
etc.) por entidades carregadas. O resultado final é idêntico à instrução com Include
s, exceto por uma diferença importante:as coleções filhas não são marcadas como carregadas no gerenciador de estado da entidade, então o EF tentará acionar o carregamento lento se você as acessar. É por isso que é importante desativar o carregamento lento. Na realidade, você terá que descobrir qual combinação de
Include
e Load
declarações funcionam melhor para você. Outros aspectos a serem considerados
Cada
Include
também aumenta a complexidade das consultas, de modo que o otimizador de consultas do banco de dados terá que se esforçar cada vez mais para encontrar o melhor plano de consulta. Em algum momento, isso pode não funcionar mais. Além disso, quando alguns índices vitais estão ausentes (especialmente em chaves estrangeiras), o desempenho pode ser prejudicado pela adição de Include
s, mesmo com o melhor plano de consulta. Núcleo do Entity Framework
Explosão cartesiana
Por algum motivo, o comportamento descrito acima, consultas UNIONed, foi abandonado a partir do EF core 3. Agora, ele cria uma consulta com junções. Quando a consulta é em forma de "estrela", isso leva a uma explosão cartesiana (no conjunto de resultados SQL). Só consigo encontrar uma nota anunciando essa mudança, mas não diz o porquê.
Consultas divididas
Para combater essa explosão cartesiana, o núcleo 5 do Entity Framework introduziu o conceito de consultas divididas que permite o carregamento de dados relacionados em várias consultas. Ele impede a construção de um conjunto de resultados SQL massivo e multiplicado. Além disso, devido à menor complexidade da consulta, pode reduzir o tempo necessário para buscar dados, mesmo com várias viagens de ida e volta. No entanto, pode levar a dados inconsistentes quando ocorrem atualizações simultâneas.
Vários relacionamentos 1:n fora da raiz da consulta.