1. Três camadas, um mesmo objetivo
Toda procedure deste curso, no fim das contas, precisa ser chamada de algum lugar na aplicação C#. Existem três níveis de abstração comuns, do mais explícito ao mais automático:
| ADO.NET | Dapper | Entity Framework Core | |
|---|---|---|---|
| Nível de abstração | Baixo — você escreve tudo | Médio — micro-ORM | Alto — ORM completo |
| Mapeamento objeto ↔ linha | Manual | Automático (reflection) | Automático (rastreado) |
| Melhor para | Controle total, performance crítica | Maioria dos casos do dia a dia | Sistemas já modelados com EF, CRUD simples |
2. ADO.NET puro
A base sobre a qual tudo o resto é construído. Você já viu fragmentos disso nos capítulos anteriores — aqui está o padrão completo, incluindo tratamento assíncrono:
public async Task<List<Cliente>> ListarClientesAsync(string? nome, bool? ativo)
{
var clientes = new List<Cliente>();
await using var conexao = new SqlConnection(_connectionString);
await using var comando = new SqlCommand("dbo.usp_ClienteListar", conexao);
comando.CommandType = CommandType.StoredProcedure;
comando.Parameters.Add("@Nome", SqlDbType.NVarChar, 100).Value = (object?)nome ?? DBNull.Value;
comando.Parameters.Add("@Ativo", SqlDbType.Bit).Value = (object?)ativo ?? DBNull.Value;
await conexao.OpenAsync();
await using var leitor = await comando.ExecuteReaderAsync();
while (await leitor.ReadAsync())
{
clientes.Add(new Cliente
{
Id = leitor.GetInt32(leitor.GetOrdinal("Id")),
Nome = leitor.GetString(leitor.GetOrdinal("Nome")),
Email = leitor.GetString(leitor.GetOrdinal("Email")),
});
}
return clientes;
}
(object?)nome ?? DBNull.Value é o detalhe que mais pega quem começa:
um null do C# não vira automaticamente NULL
do SQL — é preciso converter explicitamente para DBNull.Value.
3. Dapper — o meio-termo mais usado na prática
Dapper mapeia o resultado direto para objetos, eliminando o loop manual de leitura, mantendo o controle explícito sobre qual SQL/procedure roda:
public async Task<IEnumerable<Cliente>> ListarClientesAsync(string? nome, bool? ativo)
{
await using var conexao = new SqlConnection(_connectionString);
return await conexao.QueryAsync<Cliente>(
"dbo.usp_ClienteListar",
new { Nome = nome, Ativo = ativo },
commandType: CommandType.StoredProcedure);
}
Dapper casa o nome de cada coluna do result set com uma propriedade de mesmo nome na classe
Cliente automaticamente — reduzindo o método inteiro a uma chamada.
Para procedures com parâmetro OUTPUT:
var parametros = new DynamicParameters();
parametros.Add("@Nome", nome);
parametros.Add("@Email", email);
parametros.Add("@NovoId", dbType: DbType.Int32, direction: ParameterDirection.Output);
await conexao.ExecuteAsync(
"dbo.usp_ClienteInserir",
parametros,
commandType: CommandType.StoredProcedure);
int novoId = parametros.Get<int>("@NovoId");
4. Entity Framework Core
EF Core é pensado em torno de DbSet<T> e LINQ, mas também chama
procedures diretamente quando necessário — útil sobretudo para escrita (INSERT/UPDATE/DELETE) e
para leituras que não mapeiam bem para uma entidade rastreada:
// Leitura — mapeia para uma entidade/DTO conhecido pelo EF
var clientes = await _contexto.Clientes
.FromSqlInterpolated($"EXEC dbo.usp_ClienteListar @Nome = {nome}, @Ativo = {ativo}")
.ToListAsync();
// Escrita — não devolve entidade, só executa
int linhasAfetadas = await _contexto.Database
.ExecuteSqlInterpolatedAsync($"EXEC dbo.usp_ClienteAtualizar @Id = {id}, @Nome = {nome}, @Email = {email}");
Use sempre FromSqlInterpolated/ExecuteSqlInterpolatedAsync
(com $"..."), nunca FromSqlRaw concatenando
strings manualmente. A versão interpolada faz o EF Core tratar cada
{variavel} como parâmetro real — a mesma proteção contra SQL
injection do capítulo 10, só que do lado do C#.
5. Qual escolher
| Cenário | Recomendação |
|---|---|
| Projeto novo, time pequeno, quer produtividade | Dapper |
| Já usa EF Core para o resto do domínio | EF Core com FromSql/ExecuteSql para os casos que fogem do LINQ |
| Biblioteca/serviço de infraestrutura, performance crítica, controle fino de conexão | ADO.NET puro |
6. Onde isso te leva
Com as três formas de integração cobertas, o último capítulo deste módulo junta tudo — do modelo de dados até a chamada em C# — num projeto prático fim a fim.