Guia Completo do SqlKata para .NET: Construindo Consultas SQL Dinâmicas de Forma Elegante

Introdução

  • Em projetos complexos, para manter a flexibilidade e legibilidaed do SQL, os desenvolvedores frequentemente precisam escrever grandes quantidades de concatenação de strings ou usar o LINQ fornecido por ORMs, mas ambos têm limitações: a concatenação manual é propensa a erros e difícil de manter; o LINQ em certos cenários gera SQL não tão intuitivo ou com desempenho inferior.
  • SqlKata é um construtor de consultas (Query Builder) leve e independente de banco de dados, que oferece:
  • API fluente, chamadas encadeadas para montar SQL
  • Compiladores alternáveis, suportando vários dialetos de banco de dados (SQL Server, PostgreSQL, MySQL, SQLite, Oracle, etc.)
  • Sintaxe legível, SQL gerado próximo ao estilo de escrita manual, facilitando depuração e manutenção

Ambiente Suportado e Instalação

  • Frameworks de destino: .NET Standard 2.0+, compatível com .NET Framework 4.6.1 e superior, .NET Core 2.1+, .NET 5/6/7/8+.
  • Instale o pacote NuGet:
Install-Package SqlKata

  • Se precisar de suporte de execução integrado (com integração Dapper), instale:
Install-Package SqlKata.Execution

  • Importe os namespaces no código:
using SqlKata;
using SqlKata.Compilers;
using SqlKata.Execution;

Drivers de Banco de Dados

Para o banco de dados de destino, instale o provedor ADO.NET correspondente:

  • SQL Server: System.Data.SqlClient
  • MySQL: MySql.Data ou MysqlConnector
  • PostgreSQL: Npgsql
  • SQLite: System.Data.SQLite
  • Oracle: Oracle.ManagedDataAccess
  • Firebird: FirebirdSql.Data.FirebirdClient
Configuração do Projeto

Em projetos ASP.NET Core, você pode configurar o QueryFactory através de injeção de dependência (DI):

using Microsoft.Extensions.DependencyInjection;
using SqlKata;
using SqlKata.Execution;
using System.Data.SqlClient;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<QueryFactory>(sp =>
        {
            var conexao = new SqlConnection("sua_string_de_conexao");
            var compilador = new SqlServerCompiler();
            return new QueryFactory(conexao, compilador);
        });
    }
}

Funcionalidades Principais

Construtor de Consultas
  • Consulta básica:
var consulta = new Query("Usuarios")
    .Select("Id", "Nome", "Email")
    .Where("Ativo", true)
    .OrderByDesc("CriadoEm")
    .Limit(10, 20);  // OFFSET 10 ROWS FETCH NEXT 20 ROWS

  • Montagem encadeada: suporta métodos como .Where(), .OrWhere(), .WhereIn(), .WhereBetween(), .Join(), .GroupBy(), .Having() e outras cláusulas comuns.
  • Consulta condicional

Use o método Where para adicionar condições:

var veiculos = await db.Query("veiculos")
    .Where("preco", ">", 20000)
    .Where("nome", "like", "%Audi%")
    .GetAsync<Veiculo>();

  • Consulta dinâmica

Construa consultas dinamicamente com base em condições:

public async Task<IEnumerable<Veiculo>> PesquisarVeiculos(string textoBusca, int? precoMaximo)
{
    var query = db.Query("veiculos");

    if (!string.IsNullOrEmpty(textoBusca))
    {
        query.WhereLike("nome", $"%{textoBusca}%");
    }

    if (precoMaximo.HasValue)
    {
        query.Where("preco", "<=", precoMaximo.Value);
    }

    return await query.GetAsync<Veiculo>();
}

  • Junções (JOIN)

Suporta vários tipos de junções (como inner join, left join):

var query = db.Query("Curso")
    .Join("Departamento", "Departamento.ID", "Curso.DepartamentoID")
    .LeftJoin("Instrutor", "Instrutor.ID", "Curso.InstrutorID")
    .Select("Curso.Titulo", "Departamento.Nome as NomeDepartamento")
    .Where("Departamento.ID", 1);

var resultados = await query.GetAsync();

  • Subconsultas e consultas complexas

Suporta subconsultas e condições aninhadas:

var ultimaCompraQuery = db.Query("Transacoes")
    .Where("Tipo", "Compra")
    .GroupBy("IdUsuario")
    .SelectRaw("MAX([Data]) as DataUltimaCompra");

var usuarios = await db.Query("Usuarios")
    .Include("UltimaCompra", ultimaCompraQuery)
    .ForPage(1, 10)
    .GetAsync();

  • Inserção, atualização e exclusão

SqlKata também pode executar operações de inserção, atualização e exclusão:

// Inserção
var insertQuery = db.Query("veiculos").AsInsert(new { nome = "BMW", preco = 30000 });
await insertQuery.ExecuteAsync();

// Atualização
var updateQuery = db.Query("veiculos")
    .Where("id", 1)
    .AsUpdate(new { preco = 35000 });
await updateQuery.ExecuteAsync();

// Exclusão
var deleteQuery = db.Query("veiculos")
    .Where("id", 1)
    .AsDelete();
await deleteQuery.ExecuteAsync();

Compilador
  • Dialetos de múltiplos bancos de dados: com base no banco de destino, escolha o Compiler correspondente para gerar SQL e parâmetros adequados ao dialeto.
var compilador = new SqlServerCompiler();
var sqlResult = compilador.Compile(consulta);

// sqlResult.Sql => "SELECT [Id], [Nome], [Email] FROM [Usuarios] WHERE [Ativo] = @p0 ORDER BY [CriadoEm] DESC OFFSET @p1 ROWS FETCH NEXT @p2 ROWS ONLY"
// sqlResult.NamedBindings => { p0 = true, p1 = 10, p2 = 20 }

  • Segurança parametrizada: todas as entradas são automaticamente convertidas em parâmetros, prevenindo SQL injection.

Camada de Execução Estendida

  • Integração com Dapper: através de QueryFactory, pode executar diretamente e mapear resultados.
// Criar conexão e fábrica
using var conexao = new SqlConnection(connectionString);
var compilador = new SqlServerCompiler();
var db = new QueryFactory(conexao, compilador);

// Consultar único registro
var usuario = await db.Query("Usuarios")
                   .Where("Id", 123)
                   .FirstOrDefaultAsync<Usuario>();

// Consultar lista
var usuariosAtivos = await db.Query("Usuarios")
                          .Where("Ativo", true)
                          .GetAsync<Usuario>();

// Inserir e retornar ID auto-incrementado
var novoId = await db.Query("Usuarios")
                    .InsertGetIdAsync<int>(new {
                        Nome = "Carlos",
                        Email = "carlos@example.com",
                        CriadoEm = DateTime.UtcNow
                    });

  • Suporte a transações: use db.Transaction(...) no QueryFactory ou passe manualmente IDbTransaction.

Mistura com SQL Nativo

  • SQL bruto: pode inserir fragmentos nativos na consulta ou executar instruções totalmente personalizadas.
var query = new Query()
    .FromRaw("Usuarios u INNER JOIN Pedidos o ON u.Id = o.IdUsuario")
    .SelectRaw("u.Id, u.Nome, COUNT(o.Id) AS TotalPedidos")
    .GroupByRaw("u.Id, u.Nome");

  • Funções personalizadas: insere chamadas de função conforme necessário, como WhereRaw("DATEDIFF(day, CriadoEm, GETDATE()) < 30").

API Comum Detalhada

API Descrição
new Query(tabela) Cria objeto de consulta para tabela especificada
.Select(cols…) Especifica colunas a serem consultadas
.Where(col, op, val) Adiciona condição WHERE (suporta omitir op, padrão é =)
.OrWhere(...) Adiciona condição OR
.WhereIn(col, array) WHERE col IN (…)
.WhereBetween(col, lo, hi) WHERE col BETWEEN lo AND hi
.Join(tabela, c1, op, c2) Inner join
.LeftJoin(…)/.RightJoin Left/Right join
.GroupBy(cols…) Agrupamento
.Having(...) Cláusula HAVING
.OrderBy(col) Ordenação ascendente
.OrderByDesc(col) Ordenação descendente
.Limit(offset, count) Paginação (SQL Server usa OFFSET…FETCH, MySQL/PG usa LIMIT)
.Compile(compilador) Gera SQL texto e parâmetros
QueryFactory.GetAsync<T>() Executa consulta e mapeia para lista de entidades
InsertAsync(objeto) Insere novo registro
InsertGetIdAsync<T>(objeto) Insere e retorna chave primária auto-incrementada
UpdateAsync(objeto) Atualiza registro com base na chave primária da entidade
DeleteAsync(objeto) Exclui registro com base na chave primária da entidade
QueryFactory.StatementAsync(sql, params) Executa qualquer instrução SQL

Princípio de Funcionamento

  • Construção de consulta: SqlKata usa a classe Query para representar consulta SQL, construindo uma árvore de consulta (Query Tree) através de métodos encadeados.
  • Compilador: Compiler (como SqlServerCompiler, PostgresCompiler) converte a árvore de consulta em instrução SQL específica do banco de dados, gerando consultas parametrizadas.
  • Execução: O pacote SqlKata.Execution executa o SQL compilado através da classe XQuery e Dapper, mapeando resultadso para objetos C#.
  • Parametrização: SqlKata usa por padrão consultas parametrizadas, prevenindo SQL injection.

Desempenho e Comparação

  • Com SQL manual:
  • SqlKata tem sobrecarga muito leve na montagem de SQL e geração de parâmetros, na execução real difere muito pouco de Dapper + SQL manual.
  • A ventagem está na legibilidade e manutenibilidade, com menos erros de concatenação e complicações de bind de parâmetros.
  • Com LINQ to SQL / EF Core:
  • LINQ em cenários complexos de junções e subconsultas geralmente gera SQL mais verboso, com otimização de desempenho limitada.
  • SqlKata gera SQL quase idêntico ao manual, permitindo controle mais preciso do uso de índices e planos de execução.
  • Compatibilidade entre bancos: mesma lógica de construção de consulta, pode alternar bancos de dados com diferentes Compilers, reduzindo código duplicado e custo de manutenção.

Projeto Prático

using Microsoft.AspNetCore.Mvc;
using SqlKata;
using SqlKata.Execution;
using System.Data.SqlClient;
using System.Threading.Tasks;

public class Veiculo
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Preco { get; set; }
}

[ApiController]
[Route("api/veiculos")]
public class VeiculosController : ControllerBase
{
    private readonly QueryFactory _db;

    public VeiculosController(QueryFactory db)
    {
        _db = db;
    }

    [HttpGet]
    public async Task<IActionResult> Pesquisar([FromQuery] string textoBusca, [FromQuery] int? precoMaximo)
    {
        var query = _db.Query("veiculos");

        if (!string.IsNullOrEmpty(textoBusca))
        {
            query.WhereLike("nome", $"%{textoBusca}%");
        }

        if (precoMaximo.HasValue)
        {
            query.Where("preco", "<=", precoMaximo.Value);
        }

        var veiculos = await query.GetAsync<Veiculo>();
        return Ok(veiculos);
    }
}

Configuração inicial:

builder.Services.AddTransient<QueryFactory>(sp =>
{
    var conexao = new MySqlConnection("Server=localhost;Database=master;User Id=root;Password=");
    var compilador = new MySqlCompiler();
    return new QueryFactory(conexao, compilador);
});

Recursos e Documentação

  • Documentação oficial: https://sqlkata.com
  • Repositório GitHub: https://github.com/sqlkata/querybuilder
  • Pacotes NuGet:
  • SqlKata: https://www.nuget.org/packages/SqlKata
  • SqlKata.Execution: https://www.nuget.org/packages/SqlKata.Execution

Tags: SqlKata C#.NET Query Builder SQL Dinâmico banco de dados

Publicado em 6-19 06:50