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 oLINQfornecido porORMs, mas ambos têm limitações: a concatenação manual é propensa a erros e difícil de manter; oLINQem certos cenários geraSQLnã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,
SQLgerado 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.1e 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.SqlClientMySQL: MySql.DataouMysqlConnectorPostgreSQL: NpgsqlSQLite: System.Data.SQLiteOracle: Oracle.ManagedDataAccessFirebird: 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
Compilercorrespondente para gerarSQLe 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
SQLinjection.
Camada de Execução Estendida
- Integração com
Dapper: através deQueryFactory, 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(...)noQueryFactoryou passe manualmenteIDbTransaction.
Mistura com SQL Nativo
SQLbruto: 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:
SqlKatausa a classeQuerypara representar consultaSQL, construindo uma árvore de consulta (Query Tree) através de métodos encadeados. - Compilador:
Compiler(comoSqlServerCompiler,PostgresCompiler) converte a árvore de consulta em instruçãoSQLespecífica do banco de dados, gerando consultas parametrizadas. - Execução: O pacote
SqlKata.Executionexecuta oSQLcompilado através da classeXQueryeDapper, mapeando resultadso para objetosC#. - Parametrização:
SqlKatausa por padrão consultas parametrizadas, prevenindoSQLinjection.
Desempenho e Comparação
- Com
SQLmanual: SqlKatatem sobrecarga muito leve na montagem deSQLe geração de parâmetros, na execução real difere muito pouco deDapper+SQLmanual.- 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: LINQem cenários complexos de junções e subconsultas geralmente geraSQLmais verboso, com otimização de desempenho limitada.SqlKatageraSQLquase 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/SqlKataSqlKata.Execution:https://www.nuget.org/packages/SqlKata.Execution