Veja como melhorar a performance do Entity Framework
Boas práticas para melhorar a performance do EF
Hoje venho compartilhar através deste artigo, boas práticas que tenho aplicado em meu projetos com Entity Framework com a finalidade de melhorar sua performance.
Irei dar algumas dicas que poderão ser utilizados para quem utiliza outro tipo de ORM sem ser o Entity Framework.
São dicas simples que farão toda a diferença na performance de sua aplicação. Vamos as dicas!
Não deixe a instância de seus DbContexts abertas muito tempo.
DbContext é responsável por manipular os registros do banco de dados gerenciando todo o ciclo de vida dessas informações em memória. Com isso ele utiliza vários recursos como cache de entidades, controles de instâncias, controle de alterações, versionamentos de alterações em memória e etc. Pelo que podemos ver o DbContext é responsável por gerenciar uma série de recursos e informações, se você fizer mal uso dele, vai acabar consumindo muito mais recursos do que o necessário, ou seja, você poderá ter muitos dados em memória sem utilização aguardando o Garbage Collector passar para liberar este espaço.
Para garantirmos uma boa utilização do DbContext, aconselho sempre utilizar o mesmo dentro de um bloco Using, assim ao final do bloco o Dispose() será executado automáticamente, evitando de esquecer de tira-lo da memória.
ex:
1 2 3 |
using (var context = new LojaContext()) { //Aqui vem a implementação } |
Utilize o Change Tracking somente quanto for necessário
Sabemos que o DbContext controla o estado de cada entidade que está sendo manipulada, assim ele sabe se algum registro foi alterado, excluído ou até mesmo inserido. Só que muita das vezes nós pegamos algumas informações que iremos somente utilizar no modo consulta, ou seja, não temos intenção de alterar aquela informação. Neste cenário bem específico que só queremos pegar os dados e exibir em um DataGrid, GridView, Combobox ou até mesmo um relatório, não precisamos deixar ativo o recurso Change Tracking. Para desabilitar este recurso é bem simples, basta fazermos o uso do método “AsNoTracking()” em suas consultas, assim ele não irá mapear qualquer tipo de alteração realizada em cada registro. Veja um exemplo do uso do método:
1 2 3 4 5 6 7 8 |
using (var context = new LojaContext()) { var query = from q1 in context.Pedidos where q1.Id == 1 select q1; var result = query.AsNoTracking().ToList(); } |
Queries muito complexas
Sabemos que o Entity Framework converte as queries de LINQ para código SQL, e isso funciona muito bem. Mas em casos de queries muito complexas, que acabam trabalhando com recursividade, ou que acaba utilizando comando analíticos, o uso do LINQ não é muito aconselhável.
Neste cenário eu costumo utilizar views ou procedures, com isso eu acabo tendo vários tipos de benefícios que o próprio banco de dados me oferece, como por exemplo realizar cache do algorítimo de minhas consultas SQL, criação do plano de execução e etc.
Cuidado para não executar a queries antes da hora através dos operadores
Ao montarmos nossa query é muito comum utilizarmos alguns comandos como Count(), ToList(), ToArray(), ToSequence(), First(), FirstOrDefault(), Last() e LastOrDefault(). O que muitos não sabem é que estes métodos forçam a execução das queries no banco de dados.
Se fizer mal uso desses operadores, você poderá mandar executar sua querie mais de uma vez no banco de dados sem necessidade.
Veja o exemplo abaixo. Ele apresenta uma query bastante simples que faz um JOIN entre duas entidades de um mesmo contexto. Esse cenário é bastante simples e comum de ser encontrado em qualquer sistema. Mas se notarmos o uso dos operadores ToList() veremos que eles são utilizados dentro da query. O mau posicionamento destes operadores dentro da query afeta diretamente o modo como ela será executada.
1 2 3 4 5 6 7 8 |
using (var context = new ProgramContext()) { var query = from p in context.Pilots.ToList() join c in context.FlightCompanies.ToList() on p.CompanyId equals c.Id select new { Pilot = p.Name, Company = c.Name }; var result = query; } |
Ao invés do join entre as entidades ser executado no banco de dados ele será executado em memória, exigindo que todos os registros de “Pilots” e “FlightCompanies” sejam carregados em memória (mesmo que não exista nenhuma relação entre eles), para que então, os dados sejam relacionados.
Erroneamente pode-se imaginar que isso não faz diferença alguma, mas isso faz toda a diferença entre um sistema rápido e performático, ao invés de um sistema lento e que consome muito mais memória do que o necessário.
O modo correto de executar a query é o modo apresentado abaixo:
1 2 3 4 5 6 7 8 |
using (var context = new ProgramContext()) { var query = from p in context.Pilots join c in context.FlightCompanies on p.CompanyId equals c.Id select new { Pilot = p.Name, Company = c.Name }; var result = query.ToList(); } |
Utilize o AsEnumerable() somente quando for necessário
Cuidado ao usar o operador AsEnumerable() sem que ele realmente seja necessário.
Veja o erro apresentado abaixo:
1 2 3 4 5 6 7 8 |
using (var context = new ProgramContext()) { var query = from p in context.Pilots join c in context.FlightCompanies on p.CompanyId equals c.Id select new { Pilot = p.Name, Company = c.Name }; var result = query.AsEnumerable().ToList(); } |
O uso do método AsEnumerable() realmente não faz sentido nessa query, pois o retorno da query já implementa a interface IQueryable, que por sua vez implementa a interface IEnumerable, que é complementada pelo extension method Enumerable.ToList. Ele pode realmente ser removido da query e irá melhorar a performance, pois será um método a menos a ser executado sobre todo o conjunto retornado pelo banco de dados.
Evite retornar mais registros do que o necessário
Outro erro é forçar o banco de dados retornar mais registros do que o necessário. A query abaixo demonstra este cenário no qual todo um conjunto de registros é retornado, mas apenas um registro é utilizado.
1 2 3 4 5 6 7 8 |
using (var context = new ProgramContext()) { var query = (from p in context.Pilots join c in context.FlightCompanies on p.CompanyId equals c.Id select new { Pilot = p.Name, Company = c.Name }).ToList(); var result = query.First(); } |
Note que a query é construída, todos os registros são retornados, mas apenas o primeiro é utilizado, pois após a execução do operador ToList() invocamos o operador First(). Esse cenário é bastante ruim, pois mais registros do que o necessário foram trafegados entre o banco de dados e a aplicação, estes registros foram mapeados pelo Entity Framework (mais consumo de memória e processamento) e após isso utilizamos apenas o primeiro deles (comportamento resultante da execução do operador First()).
Evite de filtrar o registro somente em memória
No cenário abaixo é feita uma requisição ao banco de dados que por sua vez devolve os dados para o Entity Framework que acaba colocando tudo em memória e só depois é realizado o filtro. Este é um cenário que precisamos evitar, pois devemos já fazer a requisição para o banco de dados com o filtro já determinado. Veja abaixo o exemplo do mau uso e do indicado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//MAU USO!!! using (var context = new ProgramContext()) { var query = (from p in context.Pilots join c in context.FlightCompanies on p.CompanyId equals c.Id select new { Pilot = p.Name, Company = c.Name }).ToList(); var result = query.Where(x => x.Pilot.StartsWith("A")).OrderBy(x => x.Pilot); } //INDICADO using (var context = new ProgramContext()) { var query = from p in context.Pilots join c in context.FlightCompanies on p.CompanyId equals c.Id where p.Name.StartsWith("A") orderby p.Name select new { Pilot = p.Name, Company = c.Name }; var result = query.ToList(); } |
Cuidado com Lazy Loading
O Lazy Loading é um excelente recurso quando utilizado da forma correta. Para muitos ele funciona de forma atraente, pois carrega os dados agregados apenas quando precisamos deles (por isso o nome “lazy loading”). Mas esse comportamento pode consumir mais recursos do que o necessário em cenários específicos. Veja o exemplo abaixo:
1 2 3 4 5 6 7 8 9 10 |
using (var context = new ProgramContext()) { var query = from e in context.Employees where e.Company.Name == "AA" select e; foreach (var employe in query) { SendPayment(employe.Id, employe.BankAccount.Number); } } |
Note que está sendo passado como parâmetro para o método “SendPayment” o valor de uma propriedade de uma associação da classe Employe. Utilizando Lazy Loading neste exemplo acabaremos por ter um grande número de requisições ao banco de dados, sendo: uma requisição para selecionar as instâncias de Employee; e N requisições (dado o número de registros retornados pela primeira query) para consulta dos dados de BanckAccount. Assim, o resultado final de chamadas ao banco de dados será de N + 1 (dado que N é o número de registros de Employe).
Para reverter este cenário basta fazer uso do operador Include, indicando qual propriedade agregada deve ser retornada na query. A resolução pode ser vista abaixo:
1 2 3 4 5 6 7 8 9 10 |
using (var context = new ProgramContext()) { var query = from e in context.Employees.Include("BankAccount") where e.Company.Name == "AA" select e; foreach (var employe in query) { SendPayment(employe.Id, employe.BankAccount.Number); } } |
Dessa maneira apenas uma requisição será feita ao banco de dados, retornando os dados de Employe e os dados de BanckAccount.
Simplifique o retorno de suas consultas apenas com os dados que precisa
Traga somente as informações que irão de fatos ser usadas posteriormente.
Dado o exemplo abaixo, é fácil notar que para execução da operação apenas os campos Id e BankAccount.Number de cada Employe são suficientes. Mas, apesar disso, notamos que todos os campos de Employe e BackAccount são retornados.
1 2 3 4 5 6 7 8 9 10 |
using (var context = new ProgramContext()) { var query = from e in context.Employees.Include("BankAccount") where e.Company.Name == "AA" select e; foreach (var employe in query) { SendPayment(employe.Id, employe.BankAccount.Number); } } |
Utilizando recursos semelhantes ao NOLOCK
O comando NOLOCK não é suportado pelo Entity Framework, isto é, não existe um meio de “ligar ou desligar” o seu uso. Mas podemos alterar o nível de isolamento das transações, o que “no fim das contas”, gera os mesmos resultados. No exemplo abaixo é apresentada essa prática, onde o nível de isolamento da transação é alterado.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using (var context = new ProgramContext()) { using (var tran = context.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted)) { var query = from q1 in context.FlightAttendants where q1.Company != null && q1.Company.Name == "aa" select q1; var result = query.ToList(); tran.Commit(); } } |
Utilize Cache
Podemos utilizar Cache em consultas que irão sofrer poucas alterações, como algumas tabelas de domínio. Com isso iremos melhorar e muito a performance do nosso querido Entity Framework.
Espero que este artigo possa lhe ajudar a melhorar bastante a performance de sua aplicação.
Fonte: ferhenriquef.com
About author
You might also like
Curso SQL Server gratuito e com certificado no MVA
Share this on WhatsAppAprenda tudo sobre Sql Server e ganhe um certificado Que tal fazer um curso completo de Sql Server e ainda no final ganhar um certificado de conclusão
Aula – Utilizando code-first e migrations no EntityFramework
Share this on WhatsAppGerencie seu banco de dados através do Entity Framework Hoje resolvi fazer um vídeo para ajudar a galera que ainda não entendeu como podemos gerenciar nosso banco
Curso de MongoDb com C# em português e gratuito!
Share this on WhatsAppFala pessoal, tudo certo com vocês? Bom espero que sim! Bom, já não é nenhuma novidade que o uso de banco de dados não relacionais cresce a
9 Comments
Sid
abril 29, 07:36Henrique Lobo
abril 29, 12:49paulorogerio
abril 29, 20:02Lailson
abril 29, 14:21Leonardo Lima
maio 06, 15:30Guih
junho 17, 02:24paulorogerio
junho 18, 20:16GabrielScavassa
julho 12, 13:31Confira as novidades do Entity Framework Core 5.0 - I love code
fevereiro 19, 21:36