Dapper.FluentMap 3.0.3

dotnet add package Dapper.FluentMap --version 3.0.3
                    
NuGet\Install-Package Dapper.FluentMap -Version 3.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Dapper.FluentMap" Version="3.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dapper.FluentMap" Version="3.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Dapper.FluentMap" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Dapper.FluentMap --version 3.0.3
                    
#r "nuget: Dapper.FluentMap, 3.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Dapper.FluentMap@3.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Dapper.FluentMap&version=3.0.3
                    
Install as a Cake Addin
#tool nuget:?package=Dapper.FluentMap&version=3.0.3
                    
Install as a Cake Tool

FluentMap

CI Quality Gate Status .NET Standard 2.0 Coverage License: MIT GitHub stars

English | Português (Brasil)

FluentMap is an advanced mapping layer for Dapper. It lets you describe how .NET object properties map to database columns with fluent, strongly typed code, while keeping persistence attributes out of your POCOs.

FluentMap is not an ORM. It does not track entities, build arbitrary SQL, manage connections, run migrations, provide LINQ, or replace Dapper. Use it when Dapper's default name-based mapping is not enough and the mapping rules should live outside the model.

Project Status

Dapper.FluentMap is being actively modernized again. Version 3.0 continues the original project history while preserving the core FluentMap mapping model and compatibility path.

The 3.0 line modernizes the library and adds new opt-in capabilities without turning FluentMap into an ORM or requiring existing applications to adopt the new APIs.

Coming from FluentMap 2.x?

If you used FluentMap before and are returning to the project, the important compatibility points are:

  • existing EntityMap<T> mappings remain supported;
  • FluentMapper.Initialize(...) remains supported;
  • normal Dapper.Query<T>() calls continue to work with root-level mappings installed through the historical static API;
  • most existing root-level maps should not need source changes;
  • most 3.0 capabilities are opt-in, so you do not need to rewrite working mappings just because newer APIs exist.

See MIGRATION.md for the recommended 2.x to 3.0 migration path and the behavioral differences worth reviewing.

What's New in 3.0

FluentMap 3.0 modernizes the original project without changing its core purpose. In addition to the historical fluent mapping API, the 3.0 line adds opt-in support for:

  • immutable constructor mapping improvements;
  • nested object and value object materialization;
  • mapping profiles for alternate SQL shapes;
  • mapped QueryMultiple, unbuffered reads and async streaming;
  • property conversion metadata and diagnostics;
  • source-generated map registration and supported materializers;
  • Roslyn analyzers for mapping diagnostics;
  • isolated immutable configuration and dependency injection;
  • richer persistence metadata consumed by the Dommel integration;
  • trimming/AOT-aware registration and diagnostics;
  • modern compatibility tests, provider harnesses, benchmarks, CI and package validation.

Existing mappings remain the compatibility baseline. Adopt the newer APIs only when they solve a concrete problem.

Positioning

Use FluentMap for:

  • explicit property-to-column maps;
  • conventions and naming policies;
  • ignored properties;
  • immutable constructor mapping;
  • opt-in nested object and value object materialization;
  • mapping profiles for alternate SQL shapes;
  • generated map registration/materialization where supported;
  • persistence metadata consumed by integrations such as Dommel;
  • isolated configuration and dependency injection for FluentMap-controlled materialization.

Do not use FluentMap as an ORM, CRUD framework, query builder, unit of work, or database abstraction.

Installation

Install the package that matches the feature set you need:

Package purpose NuGet PackageId
Core Dapper.FluentMap
Dommel integration Dapper.FluentMap.Dommel
Dependency Injection FluentMap.DependencyInjection
Roslyn analyzers FluentMap.Analyzers
Source generators FluentMap.Generators
dotnet add package Dapper.FluentMap
dotnet add package Dapper.FluentMap.Dommel
dotnet add package FluentMap.DependencyInjection
dotnet add package FluentMap.Analyzers
dotnet add package FluentMap.Generators

The FluentMap.* PackageIds are distribution identities only. They do not rename the existing assemblies, C# namespaces or public APIs.

The public packages target netstandard2.0. See COMPATIBILITY.md before adopting a release candidate.

Quick Start

using Dapper;
using Dapper.FluentMap;
using Dapper.FluentMap.Mapping;

public sealed class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public sealed class CustomerMap : EntityMap<Customer>
{
    public CustomerMap()
    {
        Map(customer => customer.Id).ToColumn("customer_id");
    }
}

FluentMapper.Initialize(config =>
{
    config.AddMap<CustomerMap>();
});

var customer = connection.QuerySingle<Customer>(
    "SELECT 7 AS customer_id, 'Ada' AS Name;");

Call FluentMapper.Initialize(...) during application startup and treat the effective global configuration as read-only once queries begin.

Mapping

Create maps by deriving from EntityMap<TEntity>:

public sealed class ProductMap : EntityMap<Product>
{
    public ProductMap()
    {
        Map(product => product.Id).ToColumn("product_id");
        Map(product => product.Name).ToColumn("product_name", caseSensitive: false);
        Map(product => product.TransientValue).Ignore();
    }
}

Explicit mappings take precedence over conventions. Unmapped root members fall back to Dapper's normal behavior.

Conventions and naming policies cover repeated patterns:

using Dapper.FluentMap.Conventions;
using Dapper.FluentMap.Naming;

public sealed class PrefixConvention : Convention
{
    public PrefixConvention()
    {
        Properties().Configure(property => property.HasPrefix("col"));
    }
}

FluentMapper.Initialize(config =>
{
    config.AddConvention<PrefixConvention>().ForEntity<Customer>();
    config.UseNamingPolicy(NamingPolicy.SnakeCase, caseSensitive: false)
        .ForEntity<Order>();
});

Available naming policies include Identity, SnakeCase, Prefix(...), Suffix(...), Custom(...), Then(...), WithPrefix(...) and WithSuffix(...).

Immutable Types

FluentMap participates in Dapper constructor mapping for root-level explicit mappings:

public sealed class Customer
{
    public Customer(int id, string fullName)
    {
        Id = id;
        FullName = fullName;
    }

    public int Id { get; }
    public string FullName { get; }
}

public sealed class CustomerMap : EntityMap<Customer>
{
    public CustomerMap()
    {
        Map(customer => customer.Id).ToColumn("customer_id");
        Map(customer => customer.FullName).ToColumn("full_name");
    }
}

Use QueryMapped* when FluentMap must construct nested immutable objects or value objects.

Nested Objects

Nested member paths use the same Map(...) API:

public sealed class CustomerMap : EntityMap<Customer>
{
    public CustomerMap()
    {
        Map(customer => customer.Id).ToColumn("customer_id");
        Map(customer => customer.Address.City).ToColumn("city");
    }
}

var customer = connection.QueryMappedSingle<Customer>(
    "SELECT 7 AS customer_id, 'Sao Paulo' AS city;");

Nested object materialization is opt-in through QueryMapped*, ReadMapped*, QueryMultipleMapped and streaming helpers. Normal Dapper.Query<T>() remains root-level Dapper materialization.

Value Objects

For scalar value objects mapped as one database value, prefer a Dapper TypeHandler<T>:

Map(customer => customer.Cpf).ToColumn("cpf");

For value objects mapped through components, FluentMap-controlled materialization can call matching public constructors:

public sealed class CustomerMap : EntityMap<Customer>
{
    public CustomerMap()
    {
        Map(customer => customer.Id).ToColumn("customer_id");
        Map(customer => customer.Cpf.Number).ToColumn("cpf");
    }
}

var customer = connection.QueryMappedSingle<Customer>(
    "SELECT 1 AS customer_id, '12345678909' AS cpf;");

Factory methods are not used by the current materializer.

Profiles

Profiles are opt-in mappings for the same entity under different SQL shapes:

using Dapper.FluentMap.Mapping;

public sealed class LegacyProfile : IMappingProfile
{
}

public sealed class LegacyCustomerMap :
    EntityMap<Customer>,
    IProfileMap<LegacyProfile>
{
    public LegacyCustomerMap()
    {
        Map(customer => customer.Id).ToColumn("id");
        Map(customer => customer.Name).ToColumn("legal_name");
    }
}

FluentMapper.Initialize(config =>
{
    config.AddMap<CustomerMap>();
    config.AddProfile<LegacyCustomerMap>();
});

var legacy = connection.QueryMappedSingle<Customer, LegacyProfile>(
    "SELECT 7 AS id, 'Legacy Ltd.' AS legal_name;");

Profiles are selected per FluentMap-controlled query. They do not replace the global Dapper type map for the entity.

Generated Materialization

Install FluentMap.Generators when you want generated registration for maps in the current compilation:

dotnet add package FluentMap.Generators

Then call the generated extension:

FluentMapper.Initialize(config =>
{
    config.AddGeneratedMappings();
});

The generator emits AddMap<TMap>() and AddProfile<TMap>() calls for eligible maps. For supported explicit mappings it can also register generated row materializers for the ordered column shape, including flat properties, nested paths, constructor-built value objects and statically supported read converters.

Generated materialization is an optimization. Unsupported maps, dynamic shapes, shape mismatches, instance/delegate converters and some advanced patterns use the runtime fallback.

Persistence Semantics

Persistence metadata describes write participation without changing read materialization:

Map(product => product.CreatedAt)
    .ToColumn("created_at")
    .DatabaseDefaultOnInsert();

Map(product => product.UpdatedAt)
    .ToColumn("updated_at")
    .ReadOnly();

Map(product => product.Total)
    .ToColumn("total")
    .Computed();

Ignore() keeps its historical meaning: the property is not materialized by FluentMap and is not part of generated persistence metadata. For database values that should still be selected but not written, use ReadOnly(), Computed(), DatabaseDefaultOnInsert(), ExcludeFromInsert() or ExcludeFromUpdate().

The core package stores metadata. Dommel is the current package that consumes it for generated INSERT and UPDATE behavior.

QueryMultiple / Streaming

Use FluentMap query helpers when materialization must honor nested mappings, value objects, profiles, converters or generated materializers:

var customers = connection.QueryMapped<Customer>(sql);
var customer = connection.QueryMappedSingle<Customer>(sql);
var legacy = connection.QueryMappedSingle<Customer, LegacyProfile>(legacySql);

For multiple result sets:

using var multi = connection.QueryMultipleMapped(sql);

var customers = multi.ReadMapped<Customer>();
var orders = multi.ReadMapped<Order>();

ReadMapped* consumes result sets sequentially and buffers the current result set.

For incremental processing:

foreach (var customer in connection.QueryMappedUnbuffered<Customer>(sql))
{
    Process(customer);
}

Async streaming is available on DbConnection:

await foreach (var customer in connection.QueryMappedUnbufferedAsync<Customer>(
    sql,
    cancellationToken))
{
    await ProcessAsync(customer, cancellationToken);
}

Streaming keeps the underlying reader open until enumeration completes or the enumerator is disposed.

Property Converters

Property converters are configured per mapped property and run only during FluentMap-controlled materialization:

public sealed class ProductMap : EntityMap<Product>
{
    public ProductMap()
    {
        Map(product => product.Status)
            .ToColumn("status_code")
            .ConvertFromDatabaseUsing<ProductStatusConverter, string>();
    }
}

public sealed class ProductStatusConverter :
    IReadPropertyConverter<string, ProductStatus>
{
    public ProductStatus ConvertFromDatabase(string value)
    {
        return value == "A" ? ProductStatus.Active : ProductStatus.Inactive;
    }
}

Read conversion precedence in FluentMap-controlled materialization is:

null/DBNull handling
    -> property read converter
    -> Dapper TypeHandler<TProperty>
    -> FluentMap default conversion

Write converter metadata can be configured, but it is not currently executed by Dapper or Dommel writes.

Isolated Configuration / DI

The historical static API remains supported:

FluentMapper.Initialize(config =>
{
    config.AddMap<CustomerMap>();
});

For multiple FluentMap-controlled configurations in the same process, build immutable configurations and use their runtimes:

using Dapper.FluentMap.Configuration;

var runtime = new FluentMapConfigurationBuilder()
    .AddMap<CustomerMap>()
    .Build()
    .CreateRuntime();

var customer = runtime.QueryMappedSingle<Customer>(
    connection,
    "SELECT 7 AS customer_id, 'Ada' AS Name;");

Install FluentMap.DependencyInjection for DI registration:

dotnet add package FluentMap.DependencyInjection
using Microsoft.Extensions.DependencyInjection;

services.AddFluentMap(builder =>
{
    builder.AddMap<CustomerMap>();
    builder.Configure(config => config.AddGeneratedMappings());
});

The DI package registers ImmutableFluentMapConfiguration and FluentMapRuntime as singletons. It does not register database connections, repositories, Dommel bridges or global Dapper type maps.

AOT / Trimming

FluentMap has partial trimming/AOT readiness, not full Native AOT compatibility:

Area Status
Explicit registration with AddMap<TMap>() Preferred for trimming and Native AOT scenarios.
Generated registration with AddGeneratedMappings() Preferred alternative to assembly scanning for maps in the current compilation.
Assembly scanning Reflection-based and annotated as trimming-sensitive.
QueryMapped*, ReadMapped*, QueryMultipleMapped, streaming Annotated as trimming/dynamic-code sensitive because runtime fallback can occur.

Do not treat the package as fully Native AOT safe unless your application validates the exact query path and deployment mode.

Compatibility

Current compatibility documentation lives in COMPATIBILITY.md.

Short version:

  • public packages target netstandard2.0;
  • tests currently run on net10.0;
  • Dapper range is [2.1.79,3.0.0), with 2.1.79 validated in the current matrix;
  • Dommel range is [3.5.3,4.0.0) for the optional Dommel package;
  • SQLite is validated by automated provider tests;
  • SQL Server and PostgreSQL have conditional harnesses but are not certified in CI yet;
  • MySQL/MariaDB is not validated;
  • SQL Server CE remains legacy/upstream-limited.

For users moving from FluentMap 2.x, see MIGRATION.md.

Current Limitations

  • FluentMapper.Initialize(...), normal Dapper.Query<T>() and Dommel integrations use process-wide global state.
  • Isolated runtimes apply to FluentMap-controlled materialization, not to normal Dapper queries or Dommel.
  • Dommel uses global DommelMapper resolvers/builders.
  • QueryMultipleMapped is sequential and buffered per result set; there is no QueryMultipleMappedAsync.
  • QueryMultipleMapped is not Dapper multi-mapping with splitOn.
  • FluentMap does not aggregate joined rows into graphs or maintain identity maps.
  • Write converters are metadata-only in the current Dapper/Dommel write path.
  • Generated materializers cover a supported subset and can fall back to runtime materialization.
  • Assembly scanning and runtime fallback are trimming/AOT-sensitive.
  • Value object construction uses compatible public constructors, not factory methods.

More Documentation

Contributing

Keep changes small, compatible with the public API and covered by focused tests. Dapper.FluentMap.slnx is the preferred solution for current .NET SDKs; Dapper.FluentMap.sln remains available as a compatibility fallback. Typical local validation:

When enabled, SonarQube Cloud is part of the CI quality gate, with results published at the project dashboard. Authorized SonarQube analysis requires the SONAR_CI_ENABLED=true repository variable, the SONAR_TOKEN repository secret and the project configured for CI-based analysis.

dotnet restore ./Dapper.FluentMap.slnx
dotnet build ./Dapper.FluentMap.slnx --configuration Release --no-restore
dotnet test ./Dapper.FluentMap.slnx --configuration Release --no-build

License

FluentMap is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (34)

Showing the top 5 NuGet packages that depend on Dapper.FluentMap:

Package Downloads
Dapper.FluentMap.Dommel

Dommel integration for Dapper.FluentMap that configures Dommel table, key, generated-column, and persistence metadata through fluent mappings.

ZKWeb.ORM.Dapper

Support use Dapper in ZKWeb. For more information please visit project site.

EcommerceDataPostgresql

PostgreSQL Data Layer

Hurb.SAP.Database

Package Description

Dapper.Dommel

Simple CRUD operations for Dapper.

GitHub repositories (3)

Showing the top 3 popular GitHub repositories that depend on Dapper.FluentMap:

Repository Stars
zkweb-framework/ZKWeb
A flexible web framework supports .Net Framework and .Net Core
yorek/dapper-samples
Tutorial samples that shows how to use Dapper .NET
ardalis/DotNetDataAccessTour
A tour of different data access approaches in .NET 8+.
Version Downloads Last Updated
3.0.3 0 9/10/2026
3.0.2 249 9/9/2026
3.0.0 854 9/8/2026
2.0.0 8,954,531 8/23/2020
1.8.1 988,773 5/5/2020
1.8.0 1,608,867 3/28/2019
1.7.0 493,391 11/16/2018
1.6.0 862,265 8/18/2017
1.5.4 16,229 8/17/2017
1.5.3 52,577 3/22/2017
1.5.1 95,746 7/7/2016
1.5.0 38,359 6/11/2016
1.4.1 56,381 3/1/2016
1.4.0 56,639 11/19/2015
Loading failed