Hoje vamos recordar alguns procedimentos básicos que se aplicados ao usar o EF Core vão nos garantir um melhor desempenho. |
Melhorar o desempenho é sempre um requisito importante
a considerar quando usamos uma tecnologia e isso se aplica ao
Entity
FrameworkCore. Veremos a seguir de forma resumida recomendações de como
obter um melhor desempenho usando esta tecnologia.
Um conceito muito importante ao usar o EF Core é saber como funciona o rastreamento das entidades e consultas sem rastreamento.
O comportamento de rastreamento controla se o Entity Framework Core manterá informações sobre uma instância de entidade em seu rastreador de alterações.
Se uma entidade for rastreada, quaisquer alterações detectadas na entidade persistirão no banco de dados durante o uso de SaveChanges(). O EF Core também corrigirá as propriedades de navegação entre as entidades em um resultado de consulta de rastreamento e as entidades que estão no rastreador de alterações.
Por padrão, as consultas que retornam tipos de entidade são rastreadas. O que significa que você pode fazer alterações nessas instâncias de entidade e fazer com que essas alterações persistam usando SaveChanges().
Não rastrear as consultas podem ser útil quando os resultados são usados em um cenário somente leitura. Neste contexto as consultas são mais rápidas de executar porque não há necessidade de configurar as informações de controle de alterações.
Se você não precisar atualizar as entidades recuperadas do banco de dados, uma consulta sem rastreamento deve ser usada (AsNoTracking). Você pode trocar uma consulta individual para não ser rastreada. Nenhuma consulta de rastreamento também fornecerá resultados com base no que está no banco de dados, desconsiderando quaisquer alterações locais ou entidades adicionadas.
Com isso em mente veremos a seguir de forma resumida recomendações com o objetivo de obter um melhor desempenho ao usarmos o EF Core em nossas aplicações.
1. Obtenha apenas as colunas
que você realmente precisa
Em um contexto onde precisamos obter apenas uma coluna que contém a informação
que precisamos retornar todos os dados é um desperdício de recursos.
1- Quero obter apenas o Id do Produto
var produto =
_context.Produtos.FirstOrDefault(p => p.ProdutoId == id); return produto.ProdutoId; |
Porque selecionar o retorno de todas as informações para depois retornar apenasa o Id ?
2- Retornando apenas o Id
int produtoId =
_context.Produtos.Where(p => p.ProdutoId == id) .Select(s => s.ProdutoId).FirstOrDefault(); return produtoId; |
Mais eficiente.
2. Obtenha apenas as linhas que você realmente precisa
Em consultas com contagem usando IEnumerable em segundo plano que não notamos acarreta o problema, pois estamos carregando todas as linhas e todas as linhas são rastreadas, e então fizemos a contagem nos dados da memória.
Exemplo: As consultas a seguir são equivalentes :
return _context.Produtos.ToList().Count; |
IEnumerable<Produto> produtos = _context.Produtos; return produtos.Count(); |
A seguir resolvemos o problema, não carregando todos os dados, e ainda não temos nenhum rastreamento pois estamos retornando apenas um único valor:
... return GetProdutos().Count(); ...
private IQueryable<Produto>
GetProdutos() |
Lembre-se:
3.
Desabilite o rastreamento das entidades quando possível
Quando você chama o método SaveChanges no
DbContext, o contexto precisa ser capaz de gerar os comandos apropriados para
cada objeto que está atualmente rastreando.
Assim ele precisa saber sobre o "estado" de cada objeto - seja novo ou seja um objeto existente que tenha sido modificado de alguma forma ou se esta agendado para exclusão. O Change Tracker é o mecanismo responsável por este processo.
O Change Tracker registra o estado atual de uma entidade usando um dos quatro valores:
Added - Entidades no estado Added serão inseridas como novos registros no banco de dados;
Unchanged - Nenhuma ação será tomada em relação a entidades marcadas como Unchanged;
Modified - As entidades no estado Modified terão seus valores atualizados no banco de dados para os valores de propriedade atuais;
Deleted - Entidades no estado Deleted serão removidas do banco de dados;
O rastreamento de alterações começa assim que uma entidade é carregada. Uma entidade é carregada como resultado de um retorno de uma consulta ou por estar sendo introduzida no contexto através de um dos seguintes métodos do DbContext : Add, Attach, Update e Remove, ou por ter sua propriedade de State definida na entidade de entrada retornada pela chamada do método de Entry do contexto.
Assim, o DbContext no Entity Framework é responsável pelo rastreamento das alterações feitas na entidade ou no objeto, de modo que a atualização correta é feita no banco de dados quando o método SaveChange() do contexto é chamado.
Quando recuperamos entidades usando uma consulta de objeto, o Entity Framework coloca essas entidades em um cache e rastreia quaisquer alterações feitas nessas entidades até que o método SaveChanges seja chamado, então Entity Framework rastreia os resultados da consulta que retornam os tipos de entidade.
Para desabilitar o rastreamento das consultas e assim otimizar o desempenho podemos :
Exemplo:
var produto =
_context.Produtos |
Podemos também desabilitar o rastreamento a nível de instância de contexto.
Para isso usamos a propriedade QueryTrackingBehavior da classe ChangeTracker (definida como uma propriedade da classe de contexto).
_context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var produto = _context.Produtos |
No código acima estamos desabilitando o rastreamento definindo a propriedade QueryTackingBehavior da classe ChangeTracker do contexto usando a enumeração QueryTrackingBehavior que indica como os resultados serão rastreados pelo ChangeTracker.
Os valores possíveis da enumeração QueryTackingBehavior são:
3. Evite usar Joins
explicitos
Quando usamos o método Include para carregar dados
relacionados de outras entidades de forma explicita conforme mostra o código a
seguir:
var produtos = _context.Produtos .AsNoTracking() .Include(c => c.Categoria) .Where(p => p.ProdutoId == 1);
|
Mesmo se não precisamos dos dados relacionados eles sempre serão carregados.
Assim ao invés de usar o include de forma explicita podemos usar uma instrução Select para obter apenas o que desejamos :
|
Mais eficiente.
4.
Reutilize o Contexto quando possível
A idéia é simples : reutilizar o contexto e assim abrir menos conexões com o
banco de dados.
O DbContext não é thread-safe. Portanto, você não
pode reutilizar o mesmo objeto DbContext para várias consultas ao mesmo tempo
(coisas estranhas acontecem). A solução usual para isso tem sido apenas criar um
novo objeto DbContext toda vez que você precisar de um. Isso é o que
AddDbContext faz.
No entanto, não há nada de errado em reutilizar um objeto DbContext após a
conclusão de uma consulta anterior. Isso é o que
AddDbContextPool faz. Ele mantém vários objetos
DbContext ativos e fornece um não utilizado em vez de criar um novo a
cada vez.
Qual você usa fica a seu critério. Ambos funcionarão. O pooling tem alguns
ganhos de desempenho. No entanto, a documentação avisa que, se você usar alguma
propriedade privada em sua classe DbContext que não deve ser
compartilhada entre consultas, você não deve usá-la. Imagino que isso seja muito
raro, portanto, o pooling deve ser apropriado na maioria dos casos.
Exemplo de uso:
builder.Services.AddDbContextPool<AppDbContext>(options =>
options.UseMySql(mySqlConnection,
ServerVersion.AutoDetect(mySqlConnection)));
|
Antes de fazer a configuração, verifique a documentação da Microsoft para o tamanho do pool padrão para EF CORE, ele pode ser diferente para diferentes versões do EF CORE.
5 - Utilize consultas compiladas sempre que necessário
Quando um aplicativo executa muitas vezes consultas estruturalmente similares no Entity Framework, geralmente é possível melhorar o desempenho compilando a consulta uma única vez e executando-a várias vezes com parâmetros diferentes.
A classe CompiledQuery fornece a compilação e o cache de consultas para reutilização. Conceitualmente, essa classe contém um método CompiledQuery de Compile com várias sobrecargas. Chame o método Compile para criar um novo delegado para representar a consulta compilada.
Os métodos Compile, com ObjectContext e valores de parâmetro, retornam um delegado que gera um resultado (como uma instância IQueryable<T>). A consulta é compilada somente uma vez durante a primeira execução. As opções de mesclagem definidas para a consulta no momento da compilação não podem ser alteradas posteriormente. Uma vez compilada a consulta, você só pode fornecer parâmetros de tipo primitivo, mas não pode substituir partes da consulta que alterariam o SQL gerado.
Assim é uma boa prática realizar consultas usando consultas compiladas se a consulta for freqüentemente usada para buscar os registros do banco de dados.A consulta será lenta na primeira vez, mas depois ela vai melhorar o desempenho de forma significativa. Para compilar uma consulta podemos usar o método Compile da classe CompiledQuery.
Assim para recuperar os detalhes dos clientes várias vezes baseado em um critério como a cidade podemos fazer essa consulta compilada da seguinte forma:
- Consulta simples (não compilada)
IQueryable clientes = from cliente in _context.Clientes where cliente.Cidade == "Lins" select cliente; |
- Consulta Compilada
static readonly Func<AdventureWorksEntities, Decimal> s_compiledQuery3MQ =
CompiledQuery.Compile<AdventureWorksEntities, Decimal>(
ctx => ctx.Products.Average(product => product.ListPrice));
static void CompiledQuery3_MQ()
{
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
Decimal averageProductPrice = s_compiledQuery3MQ.Invoke(context);
Console.WriteLine("O Preco medio dos produtos é $: {0}", averageProductPrice);
}
}
|
O exemplo a seguir compila e invoca uma consulta que retorna a média dos preços na lista de produtos como um valor Decimal.
Regras de uso para consultas compiladas :
1. Crie um delegado Func estático que aceitará DbContext e retornará
IEnumerable;
2. Você não pode retornar IQuerable de consultas compiladas;
3. As consultas compiladas só podem ser usadas em um único modelo do EF Core. Às
vezes, diferentes instâncias de contexto do mesmo tipo podem ser configuradas
para usar modelos diferentes; a execução de consultas compiladas neste cenário
não é suportada.
4. Ao usar parâmetros em consultas compiladas, use parâmetros escalares simples.
Expressões de parâmetros mais complexas — como acessos de membros/métodos em
instâncias — não são suportadas.
E estamos conversados...
"Porque todos sois filhos de Deus pela fé
em Cristo Jesus.Porque todos quantos fostes batizados em Cristo já vos
revestistes de Cristo."
Gálatas 3:26,27
Referências: