Dicas de performance com Entity Framework

0

O Entity Framework é uma ferramenta ORM da Microsoft madura e testada pelo mercado que pode ser usada para aplicações que usam o .NET Framework, porém existem muitas maneiras de se obter maior performance com a ferramenta utilizando algumas dicas que vão fazer sua aplicação consumir menos recurso e/ou aumentar a performance.

Filtre as entidades no banco de dados ao invés de filtrá-las na memória:

//LINQ to Objects: Todos os clientes são retornados do banco de dados e filtrados na memória

var clientes = ctx.Clientes.ToList().Where(x => x.Nomes.Any());

//LINQ to Entities: os clientes são filtrados no banco de dados e e somente depois retornados para a memória

var clientes = ctx.Clientes.Where(x => x.Nomes.Any()).ToList();

 

As consultas feitas usando LINQ to Entities retornam coleções que são executadas imediatamente após você chamar um método ToList, ToArray ou ToDictionary. Após disso, o resultado é materializado e armazenado no espaço de memória do processo.

Como essas consultas são instâncias de IEnumerable<T>, elas podem ser manipuladas com operadores padrão LINQ to Objects sem que você perceba. Isso significa que usar LINQ to Entities faz com que a consulta seja executada no banco de dados enquanto que usar LINQ to Objects faz com que a consulta seja executada pelo processo atual, ou seja, na memória.

Desabilite o monitoramento de resultados das consultas de exibição de dados pelo contexto utilizando: AsNoTracking

O Entity Framework possui um cache de primeiro nível (ou local) onde todas as entidades conhecidas por um contexto EF, carregadas a partir de consultas ou explicitamente adicionadas, são armazenadas.

Isso acontece de forma que quando chegar a hora de salvar as alterações no banco de dados, o EF passa por esse cache e verifica quais entidades precisam ser salvas, ou seja, quais precisam ser inseridas, atualizadas ou excluídas.

O que acontece se você carregar um número de entidades apenas de consultas quando EF tem que salvar ?

Ele vai precisar passar por todos essas entidades, para verificar quais mudaram, e isso constitui em um aumento de memória. Se você não precisa acompanhar as entidades que resultam de uma consulta, pois elas são apenas para exibição, você deve aplicar o método de extensão AsNoTracking.

//sem caching
var clientes = ctx.Clientes.AsNoTracking().ToList();

var clientesComPendencia = ctx.Clientes.Where(x => x.Pendencias.Any()).AsNoTracking().ToList();

var localClientes = ctx.Clientes.Local.Any(); 

 

 

Desabilite a detecção automática de alterações nas entidades

Por padrão, o método DetectChanges é chamado automaticamente em várias ocasiões, como quando uma entidade é explicitamente adicionada ao contexto, quando uma consulta é executada, etc. Isso resulta na degradação do desempenho sempre que um grande número de entidades está sendo monitorado.A solução alternativa é desativar o controle automático de alterações definindo a propriedade AutoDetectChangesEnabled com o valor adequado.

// desativando a monitoração automática de alterações
ctx.Configuration.AutoDetectChangesEnabled = false;

 

Lembre-se que sempre que SaveChanges for invocado, ele vai chamar DetectChanges e tudo vai funcionar corretamente.

Utilize Lazy e Eager Loading de forma apropriada

O mecanismo Lazy Load retarda a inicialização de um objeto até ao ponto que ele for realmente acessado pela primeira vez. Pela definição você pode achar que o Lazy Load é um bom recurso e o fato de ser habilitado por padrão parece que é um aval para sua utilização mas ele esconde um problema gravíssimo que se manifesta em consultas quanto estão envolvidas entidades com associações do tipo um-para-muitos.

O mais grave deles é conhecido como SELECT N+1 que se manifesta assim : você emite uma consulta base que retorna N elementos e, em seguida, serão emitdas outras N consultas, uma para cada referência/coleção que você deseja acessar. Dessa forma quando você realmente precisar acessar uma propriedade de referência ou navegar através de todas as entidades filhas presentes em uma coleção, você deve carregá-las usando o Eager Loading com a entidade que as contém.

Para utilizar o Eager Loading você deve usar o método de extensão Include :

//eager load dos clientes para cada Recurso
var clientesRecursoInclude = ctx.Recursos.Include(x => x.Clientes).ToList();

//eager load do cliente para cada projeto
var clientesProjetosInclude = ctx.Projetos.Include("Cliente").ToList();

 

Dessa forma usando o Eager Loading você evita muitos problemas mas pode estar trazendo mais informações de que precisa.

Utilize projeções sempre que possível

Não sei se você sabe mas normalmente as consultas LINQ e Entity SQL retornam entidades completas; Isto é, para cada entidade, eles trazem todas as suas propriedades mapeadas (exceto referências e coleções).

Às vezes não precisamos da entidade completa, mas apenas algumas partes dela, ou mesmo algo calculado de algumas partes da entidade. Para isso, usamos projeções.

As projeções nos permitem reduzir significativamente os dados retornados permitindo-nose escolher apenas aqueles que precisamos.

Aqui estão alguns exemplos em LINQ e Entity SQL.

 //retorna os recursos e nome dos projetos somente com LINQ
var recursosProjetos = ctx.Projetos.SelectMany(x => x.ProjetoRecursos).Select(x => new { Recurso = x.Recurso.Nome, Projeto = x.Projeto.Nome }).ToList();

//retorna os nomes dos clientes e a quantidade de seus projetos com LINQ
var clientesQuantosProjetos = ctx.Clientes.Select(x => new { x.Nome, Count = x.Projetos.Count() }).ToList();

//retorna o nome do projeto e sua duração com ESQL
var projetoNomeDuracao = octx.CreateQuery<Object>("SELECT p.Nome,DIFFDAYS(p.Start, p.[End]) FROM Projetos AS p WHERE p.[End] IS NOT NULL").ToList();

 

Desabilite a validação após Salvar

As validações são acionadas para cada entidade quando o contexto está prestes a salvá-los, e se você tiver muitas entidades, isso pode levar algum tempo.

Quando tiver completa certeza de que as entidades que pretende armazenar são todas válidas, você pode desativar a sua validação, desabilitando a propriedade ValidateOnSaveEnabled.

 //desabilita a validação automática após salvar
ctx.Configuration.ValidateOnSaveEnabled = false;

Procure trabalhar com entidades desconectadas

Ao salvar uma entidade, se você precisar armazenar em uma propriedade uma referência de outra entidade para a qual você conhece a chave primária, ao invés de carregar a entidade com Entity Framework, basta atribuir a entidade a uma entidade vazia definindo apenas a propriedade identidade(Id) preenchida.

 //salva um novo projeto referenciando um cliente existente
var novoProjeto = new Projeto { Nome = "Projeto Teste", Cliente = new Cliente { ClienteId = 1 } /* ao invés de ctx.Clientes.Find(1)*/ };
ctx.Projetos.Add(novoProjeto);
ctx.SaveChanges();

 

Esse código vai funcionar pois o Entity Framework precisa somente da definição da chave estrangeira.

Da mesma forma, realizar uma exclusão você não precisa carregar a entidade previamente. Informe sua chave primária para fazer isso:

 //deleta um Cliente pelo id
//ctx.Clientes.Remove(ctx.Clientes.Find(1));

ctx.Entry(novo Cliente { ClienteId = 1 }).State = EntityState.Deleted;
ctx.SaveChanges();

 

O objetivo dessas dicas é melhorar o desempenho do Entity Framework. Faça uma análise do seu projeto e verifique se alguma delas pode ser aplicada.

Um grande abraço a todos! 🙂

 

O artigo foi baseado na fonte: http://www.macoratti.net/17/05/ef6_ddesemp1.htm

Compartilhe.

Sobre o autor

Criador do blog Código Simples e com mais 9 anos de experiência em TI, com títulos de MVP Microsoft na área de Visual Studio Development, Neo4j Top 50 Certificate, Scrum Master e MongoDB Evangelist. Atuando em funções analista, desenvolvedor, arquiteto, líder técnico e gestor de equipes. Mais informações em : http://jhonathansoares.com