EntityFramework - SQL Raw Queries

Recentemente foi lancado o EF 8, juntamente com o .NET 8 e com ele muitas novidades! Mas dentre todas, uma que ‘volta’, sim, ela já existiu no passado, é o uso de queries diretas sem uso do contexto, também conhecidas como RAW Queries.

Podemos afirmar que o seu uso é muito parecido com o Dapper, mas em um contexto do EF.

Como funciona ?

Imagine que você tem um banco de dados de Livros, como o script abaixo:

CREATE TABLE [dbo].[Editora](
	[id] [int] IDENTITY(1,1) NOT NULL,
	[Nome] [varchar](20) NOT NULL,
	[Endereco] [varchar](50) NOT NULL,
	[Numero] [varchar](10) NULL,
PRIMARY KEY CLUSTERED 
(
	[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Livro](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[Nome] [varchar](30) NOT NULL,
	[preco] [decimal](6, 2) NULL,
	[Lancamento] [datetime] NULL,
	[Id_Editora] [int] NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Livro]  WITH CHECK ADD  CONSTRAINT [Fk_Editora] FOREIGN KEY([Id_Editora])
REFERENCES [dbo].[Editora] ([id])
GO

ALTER TABLE [dbo].[Livro] CHECK CONSTRAINT [Fk_Editora]
GO

Então temos um banco de dados com Editora e Livro, precisamos então de duas classes para mapear estas tabelas no contexto do EF:

public class Editora
{
    public int ID { get; set; }
    public string? Nome { get; set; }
    public string? Endereco { get; set; }
    public string? Numero { get; set; }
}

public class Livro
{
    public int ID { get; set; }
    public string? Nome { get; set; }
    public decimal Preco { get; set; }
    public DateTime Lancamento { get; set; }
    public int Id_Editora {get; set;}
    public virtual Editora Editora { get; set; }
}

E também o contexto:

public class Contexto : DbContext
{
    public Contexto(DbContextOptions<Contexto> options): base(options) { }

    public DbSet<Editora> Editora { get; set; }
    public DbSet<Livro> Livro { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
        base.OnModelCreating(modelBuilder);
    }
}

Então se quisermos fazer uma consulta usando o Entity, podemos fazer algo assim:

var dbOptions = new DbContextOptionsBuilder<Contexto>()
            .UseSqlServer("data source=(local); initial catalog=AceleraDev; integrated security=true;trusted_connection=true;encrypt=false;multipleactiveresultsets=true;")
            .UseLazyLoadingProxies();

var db = new Contexto(dbOptions.Options);

var livros = db.Livro;
foreach(var l in  livros)
{
    Console.WriteLine(l.Nome);
}

Ok, então para uma consulta no EF eu preciso criar relacionamentos, mapear tudo e depois navegar usando Proxy! Isto era o que faziamos, pois agora dá para fazer algo bem interessante!

Usando o SQL Raw

Agora vamos fazer uma consulta usando um comando SQL Direto, mas precisamos guardar o resultado em uma classe, então vamos criar uma classe Dto para isto:

public class LivroEditoraDto
{
    public string? Livro { get; set; }
    public decimal Preco { get; set; }
    public string? Editora { get; set; }
}

E agora fazemos a consulta:

var livroEditora = db.Database.SqlQuery<LivroEditoraDto>($"select l.Nome Livro,l.preco Preco,e.nome Editora from livro l, editora e where l.Id_Editora = e.id")
                        .ToList();
foreach (var l in livroEditora)
{
    Console.WriteLine($"{l.Livro} - Ed {l.Editora} - {l.Preco}");
}

Veja que a classe LivroEditoraDto nao está mapeada no contexto, ela foi usada apenas na consulta. Então o resultado da nossa consulta é “copiado” para a classe Dto e esta classe não precisa existir no Contexto.

Pense que você pode fazer queries como no Dapper e ainda utilizar todos os benefícios dos relacionamentos do EF!!!

Considerações

Agora podemos escrever nossas queries manualmente e fazer uma consulta que retorna um dado serializado em uma classe! Muito bom!!! Mas cuidado! Você está usando a sintaxe do banco de dados e isto pode quebrar a compatibilidade com outros bancos. Mesmo assim é muito interessante!!!

O código fonte deste exemplo pode ser encontrado no meu GitHub: https://github.com/carloscds/EntityFramework/tree/main/EFCore8SQLRaw

Abraços e até a próxima!