Escrever querys brutas para consulta em um banco de dados relacional usando Entity Framework Core (EF) para retornar um DTO.
Quem nunca teve problemas com Query geradas pelo Entity Framework (EF) que atire a primeira pedra. Dependendo da complexidade do que se busca pode ficar muito complicado sua performance. Usar o FromSqlRaw funciona, mas somente para o objeto mapeado no contexto (DbContext) e se eu quiser retornar um DTO (Data Transfer Object) ou uma mistureba de dados??
“Mas Dumbá, usa o Dapper!! Ta reinventando a roda por quê?” Sim, Dapper resolve, mas sou nojento e quero usar o Entity 🙂 ou simplesmente a Arquitetura da empresa ainda não permite o Dapper. Dependendo do negocio somente pacotes aprovados podem ser utilizados.
E por esse motivo, um amigo me perguntou outro dia como ele poderia resolver esse problema, sabendo que a empresa utilizava o entity e estava relutante a liberar outro pacote. Era simples, basta mapear o objeto no contexto e chamar o FromSqlRaw, mas ele ficou encucado com isso e queria algo mais simples, sem mapeamentos.
Ai que entra o pulo do gato velho!
Os caboclos das antigas sabem que por traz de tudo que abrange acesso a dados em .NET desde os primórdios é o bom e velho ADO.NET. O que o Entity, Dapper e qualquer outro ORM ou Micro ORM faz é encapsular o ADO.NET.
Propus a esse meu amigo uma solução no qual utilizo em meus QueryRepositories (Gosto de separar os repositórios, um para persistência e outro para consultas, coisas de CQRS, mas isso fica pra outro artigo).
A ideia do código abaixo é buscar os Providers do ADO que já existem no EF. Como já temos o contexto do EF injetado, vou acessar o banco de dados já configurado para o EF.
protected IList<Tdto> SelectSql<Tdto>(string sql, params object[] parameters) where Tdto: class { using var command = _context.Database.GetDbConnection().CreateCommand(); command.CommandText = sql; foreach (var parameter in parameters) { command.Parameters.Add(parameter); } _context.Database.OpenConnection(); using var result = command.ExecuteReader(); return DataReaderMapToList<Tdto>(result); }
Assim eu consigo passar a consulta Sql que preciso, passo o DTO ou Classe a para ser transformada e retorno um objeto já mapeado usando o Generics “Tdto”.
private static List<Tdto> DataReaderMapToList<Tdto>(DbDataReader dr) { List<Tdto> list = new List<Tdto>(); if (dr.HasRows) { while (dr.Read()) { var dto = Activator.CreateInstance<Tdto>(); foreach (PropertyInfo prop in dto.GetType().GetProperties()) { if (dr.GetColumnSchema().Any(x => x.ColumnName.ToUpper() == prop.Name.ToUpper())) { if (!Equals(dr[prop.Name], DBNull.Value)) { prop.SetValue(dto, dr[prop.Name], null); } } } list.Add(dto); } return list; } return new List<Tdto>(); }
Usando Reflection eu mapeio o retorno do DataReader para o DTO informado.
Como uso o puro e velho ADO isso faz com que a performance seja muito, mas muito boa. (Depende da Query né mestre, se fizer um select * em 2.000.000,00 de linhas vai demorar um mocado), mas já em melhor que 90% das consultas do EF (Eu acho). 😉
Mas se tiverem uma solução melhor, manda ai pra mim, vamos disseminar conhecimento, pois eu não sei de tudo e é sempre bom aprender.
Os códigos fontes estão no meu GitHub e os códigos como são utilizados estão no qsLibPack, pacote que utilizo para padronizar meus projetos e no qsLog, que é um projeto no qual utilizo muita coisa interessante como DDD, Mediator, Repository, TDD, Services e muito mais.
Obrigado pela leitura e duvidas é so mandar!
Deixe uma resposta
Want to join the discussion?Feel free to contribute!