Escrever querys brutas para consulta em um banco de dados relacional usando Entity Framework Core (EF) para retornar um DTO.

Por •

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 seu comentário

Your email address will not be published. Required fields are marked *