Schema estável
Tabelas e colunas são compartilhadas e não podem seguir imediatamente a nomenclatura do domínio.
Estudo de caso arquitetural · biblioteca open source
Uma tabela CUSTOMER expõe CUST_NO e LEGAL_NM; o domínio precisa falar em Customer.Id e Customer.Name. O FluentMap torna essa tradução explícita sem colocar atributos de persistência na entidade.
O cenário corporativo é ilustrativo. Ele demonstra contratos públicos da biblioteca; não representa cliente, implantação ou ganho de desempenho em produção.
Contexto e motivação
Schemas legados carregam abreviações, convenções históricas e nomes compartilhados por sistemas que não podem ser renomeados em uma única entrega. Repetir esses nomes no modelo .NET transfere o custo de persistência para regras de negócio, APIs e testes.
O objetivo não é esconder SQL nem criar um ORM completo. É manter a consulta e a conexão sob responsabilidade do Dapper, enquanto uma configuração tipada e centralizada traduz os nomes físicos para propriedades que comunicam intenção.
Restrições e atributos de qualidade
Tabelas e colunas são compartilhadas e não podem seguir imediatamente a nomenclatura do domínio.
Entidades não recebem atributos que as façam conhecer o mecanismo de persistência.
A linha 3.x preserva as principais APIs históricas da 2.x, com diferenças documentadas e recursos novos opt-in.
Compatibilidade do Dapper, certificação do Core e integração Dommel são afirmações distintas.
Decisões arquiteturais
EntityMap<T> associa expressões de propriedade a colunas. Renomear uma propriedade passa pelo compilador e a configuração pode ser validada no startup.
Convenções reduzem repetição quando o schema segue padrões. Mapas explícitos continuam adequados para exceções e têm precedência documentada.
Precedência ↗A configuração global é construída antes das consultas concorrentes e tratada como somente leitura. FluentMapRuntime permite configurações isoladas nas APIs QueryMapped*.
O pacote Dommel adiciona metadados e persistência, mas mantém estado global nos resolvers e builders. Runtimes isolados do FluentMap não isolam Dommel.
Integração Dommel ↗Source generators registram maps elegíveis e materializadores para formas suportadas. Eles não analisam SQL nem garantem toda forma de materialização; há fallback em casos não suportados.
Source generators ↗Analyzers detectam configurações observáveis em compilação. Registro dinâmico, reflexão e comportamento dependente de runtime permanecem fora dessa prova.
Analyzers ↗Implementação do cenário
using Dapper;
using Dapper.FluentMap;
using Dapper.FluentMap.Mapping;
FluentMapper.Initialize(config => config.AddMap<CustomerMap>());
FluentMapper.Validate();
const string sql = """
SELECT CUST_NO, LEGAL_NM
FROM CUSTOMER
WHERE CUST_NO = @id
""";
Customer customer = connection.QuerySingle<Customer>(sql, new { id = 42 });
public sealed class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public sealed class CustomerMap : EntityMap<Customer>
{
public CustomerMap()
{
Map(customer => customer.Id).ToColumn("CUST_NO");
Map(customer => customer.Name).ToColumn("LEGAL_NM");
}
}
O exemplo usa a API atual documentada. A conexão, a transação, a parametrização, a consulta e migrations continuam sob responsabilidade da aplicação e do Dapper.
O provider ADO.NET entrega ao Dapper um reader com CUST_NO e LEGAL_NM. O type map instalado pelo FluentMap resolve esses nomes para Id e Name. O Dapper cria e preenche Customer; o FluentMap não rastreia a entidade nem muda o SQL.
Alternativas e trade-offs
| Abordagem | Exemplo | Vantagem | Custo e melhor contexto |
|---|---|---|---|
| Aliases no SQL | SELECT CUST_NO AS Id, LEGAL_NM AS Name | Sem configuração adicional; intenção visível na consulta. | Repete aliases em várias consultas. Boa para leitura pontual ou SQL inteiramente controlado por uma única operação. |
| DTO de persistência | CustomerRow.CUST_NO → Customer.Id | Fronteira e transformação totalmente explícitas; DTO pode refletir joins e projeções. | Mais tipos e código de conversão. Boa quando o shape de persistência difere semanticamente do agregado. |
| FluentMap explícito | Map(x => x.Id).ToColumn("CUST_NO") | Reutiliza um map tipado e mantém a entidade livre de atributos. | Configuração global exige disciplina; não elimina SQL. Boa quando entidades reaparecem em várias consultas com nomes físicos estáveis. |
Evolução e manutenção
EntityMap<T>, FluentMapper.Initialize(...) e a ponte de type map permanecem. Para o caminho histórico comum, a migração tende a ser atualização coordenada de pacotes, validação e testes.
Dapper.Query<T>() normal usa o type map global do Dapper. Dommel também é global. O isolamento de FluentMapRuntime vale para as APIs mapeadas que recebem ou pertencem ao runtime.
SQLite, SQL Server, PostgreSQL, MySQL e MariaDB possuem evidência real para Core e Dommel na matriz atual. Oracle e Firebird têm evidência Core, mas não certificação Dommel.
Matriz de compatibilidade ↗A faixa atual documentada é Dapper [2.1.79,3.0.0) e Dommel [3.5.3,4.0.0). Canários experimentais não ampliam a matriz até atualização intencional e revisada.
Limitações e riscos
A biblioteca não administra conexões, transações, migrations, tracking ou desenho de queries. Configuração global tardia pode conflitar com consultas concorrentes; inicialização deve ocorrer antes do uso e ser tratada como imutável.
Compatibilidade esperada por Dapper/ADO.NET não equivale a certificação do FluentMap. Certificação Core não implica Dommel. Também não há alegação de Native AOT completo: o caminho estrito gerado tem cobertura delimitada e formas incompatíveis podem usar fallback.
Os benchmarks usam BenchmarkDotNet e SQLite em cenários definidos no repositório. O baseline versionado traz "reportOnly": true; portanto, serve como evidência exploratória e detector de tendência, não como promessa geral de ganho de desempenho entre ambientes, providers ou workloads.
Aprendizados
O valor do map não está em esconder o banco, mas em localizar a tradução. Aliases, DTOs e FluentMap permanecem opções arquiteturais; a frequência de reuso, a diferença semântica e a necessidade de isolamento indicam qual delas custa menos.
Na evolução da biblioteca, recursos opt-in permitem ampliar materialização, diagnóstico e integração sem obrigar consumidores históricos a adotar tudo. Matrizes separadas e linguagem precisa impedem que uma evidência de teste seja generalizada além do que realmente cobre.
Evidências e referências