FclEx.Dapper
2.4.0
See the version list below for details.
dotnet add package FclEx.Dapper --version 2.4.0
NuGet\Install-Package FclEx.Dapper -Version 2.4.0
<PackageReference Include="FclEx.Dapper" Version="2.4.0" />
<PackageVersion Include="FclEx.Dapper" Version="2.4.0" />
<PackageReference Include="FclEx.Dapper" />
paket add FclEx.Dapper --version 2.4.0
#r "nuget: FclEx.Dapper, 2.4.0"
#:package FclEx.Dapper@2.4.0
#addin nuget:?package=FclEx.Dapper&version=2.4.0
#tool nuget:?package=FclEx.Dapper&version=2.4.0
FclEx.Dapper
FclEx.Dapper adds focused, cache-aware CRUD and transaction helpers to Dapper while keeping connections, transactions, SQL providers, and execution boundaries visible to the caller. It is not an ORM: it does not provide change tracking, relationship management, LINQ translation, schema migrations, repositories, or a unit of work.
Installation
Install FclEx.Dapper and the ADO.NET provider used by the application:
dotnet add package FclEx.Dapper
dotnet add package Microsoft.Data.Sqlite
The package targets net472, netstandard2.0, net8.0, net9.0, and net10.0. It references Dapper and FclEx.Core, but it does not add a database provider dependency.
Quick Start
Map an entity with DataAnnotations:
[Table("widgets")]
public sealed class Widget
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string Name { get; set; } = "";
}
Use a normal provider connection. The schema must already exist; the CREATE TABLE below is included only to make the example runnable:
using var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
await connection.ExecuteAsync(
"CREATE TABLE widgets (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL)");
var id = await connection.InsertAsync(new Widget { Name = "first" });
var widget = await connection.GetAsync<Widget>(id);
await connection.BulkInsertAsync(
[
new Widget { Name = "second" },
new Widget { Name = "third" },
]);
await connection.DeleteAsync<Widget>(id);
InsertAsync<TEntity> returns the generated key as long. Use InsertAsync<TEntity, TKey> when the generated key has another type.
Providers and SQL Adapters
FclEx.Dapper recognizes these provider connection types when the corresponding provider package is installed:
| Database | Provider package | Built-in adapter | Generated-key SQL |
|---|---|---|---|
| SQL Server | Microsoft.Data.SqlClient |
SqlServerAdapter |
OUTPUT INSERTED |
| PostgreSQL | Npgsql |
NpgsqlAdapter |
RETURNING |
| Oracle | Oracle.ManagedDataAccess / Oracle.ManagedDataAccess.Core |
OracleAdapter |
RETURNING ... INTO |
| SQLite | Microsoft.Data.Sqlite |
SqliteAdapter |
last_insert_rowid() |
| MySQL | MySql.Data |
MySqlAdapter |
LAST_INSERT_ID() |
| MySQL/MariaDB | MySqlConnector |
MySqlConnectorAdapter |
LAST_INSERT_ID() |
For generated Oracle insert commands, string values without a store type name use OracleDbType.NVarchar2. Explicit store types such as VARCHAR2, NVARCHAR2, CLOB, and NCLOB retain their configured parameter types. This parameter default does not create or change database columns and does not affect ordinary Dapper calls.
For generated Npgsql commands, DateTimeOffset parameters are converted to UTC when no store type is supplied or the explicit type is TimestampTz, timestamptz, or timestamp with time zone. This preserves the instant; PostgreSQL does not retain the original offset. Timestamp / timestamp without time zone and other explicit types leave values unchanged and may reject incompatible values. The adapter uses parameter mapping, does not inspect database columns, and does not convert DateTime values. This behavior does not affect ordinary Dapper calls.
Oracle uses double-quoted, case-sensitive identifiers and : parameter placeholders. Map identifiers with their actual database casing (typically uppercase for objects created without quotes). BulkInsertAsync uses multi-row VALUES, requiring Oracle Database 23ai or later. The adapter limits each command to floor(65535 / parameterCountPerRow) rows; the shared insert pipeline also caps each batch at 500 rows. Oracle restricts identity/sequence defaults in multi-row VALUES; supply explicit keys where the column permits them, or insert such rows individually. Single-row inserts return numeric, string, and date/time generated keys using RETURNING ... INTO output parameters. Default-only inserts are supported when a single generated key is requested, using that column with DEFAULT; otherwise an explicit insertable column is required. Explicit identity values require a column defined as GENERATED BY DEFAULT; GENERATED ALWAYS columns reject explicit values.
Adapter resolution examines the runtime connection type and its base types. Register an adapter for a custom or wrapped connection type when it cannot be recognized automatically:
DapperHelper.RegisterSqlAdapter<CustomConnection>(new CustomSqlAdapter()); // add or replace
var added = DapperHelper.TryRegisterSqlAdapter<CustomConnection>(new CustomSqlAdapter());
TryRegisterSqlAdapter returns false when the exact connection type is already registered. A registered adapter must keep its SQL-affecting behavior stable because generated SQL is cached by adapter instance. Schema support follows ISqlAdapter.SupportsSchemas; unsupported adapters ignore mapped and per-call schemas.
Table, schema, and column names supplied through mappings or method arguments must come from trusted application configuration, not end-user input. Adapters quote and escape identifiers, but identifier names cannot be parameterized like data values.
Entity Mapping and Key Limits
DataAnnotationsEntityMappingSource is used by default. It supports:
[Table], includingSchema[Column], including column aliases and provider store type names[Key][NotMapped][DatabaseGenerated]withNone,Identity, orComputed
By convention, public readable and writable scalar instance properties are persistent. Static properties, indexers, read-only properties, navigation properties, and explicitly unmapped properties are excluded. A non-scalar property must declare an explicit mapping attribute to be included.
GetAsync<T> and DeleteAsync<T> require exactly one mapped key. Generated-key return from InsertAsync requires exactly one generated key. Composite-key lookup and deletion are not supported.
Implement IEntityMappingSource when mappings should be independent of DataAnnotations. A source must return the same immutable EntityMapping instance whenever the same entity type is requested because mapping identity participates in SQL cache keys:
var mapping = new EntityMapping(
typeof(Widget),
"widgets",
[
new(typeof(Widget).GetProperty(nameof(Widget.Id))!, "Id", true,
DatabaseValueGeneration.OnInsert),
new(typeof(Widget).GetProperty(nameof(Widget.Name))!, "Name"),
]);
var options = new CommandOptions { EntityMappingSource = new ApplicationMappingSource(mapping) };
var id = await connection.InsertAsync(new Widget { Name = "mapped" }, commandOptions: options);
ApplicationMappingSource in this example is an application-owned IEntityMappingSource implementation.
Commands, Connections, and Transactions
CommandOptions carries the command timeout, local transaction, adapter override, and mapping source. The same options shape is accepted by connection and transaction CRUD methods. Cancellation is supplied separately through the final optional cancellationToken parameter, which covers connection opening and command execution.
await connection.BulkInsertAsync(
entities,
commandOptions: options,
cancellationToken: cancellationToken);
Migration note: CommandOptions.CancellationToken has been removed. Pass the token directly to each asynchronous CRUD method instead. This is a breaking API change; consumers must rebuild, and calls that initialized the removed property must be updated.
CRUD helpers record the connection's initial state. A connection opened by the helper is closed before the operation returns; a connection supplied already open remains open. Transaction extension methods bind the receiver transaction to the generated command.
ExecuteInTransactionAsync starts a local transaction with ReadCommitted by default, commits after a successful callback, and attempts rollback after callback or commit failure. If both the operation and rollback fail, an AggregateException contains both exceptions.
Bulk Inserts and SQL Caching
BulkInsertAsync emits bounded multi-row INSERT commands; it does not silently execute one command per entity. A batch contains at most 500 rows and may be smaller because of provider row or parameter limits. Multiple rows with no insertable properties are rejected when the adapter cannot express an efficient bulk form.
NpgsqlAdapter converts scalar ushort, uint, and ulong parameter values to int, long, and decimal, respectively. These conversions preserve their full ranges and allow PostgreSQL integer, bigint, and numeric parameters to be inferred. byte values remain unchanged. The conversion applies to generated insert commands, including bulk inserts; it does not change ordinary Dapper calls or configure table column types. Explicit PostgreSQL unsigned types (oid, xid, xid8, cid, regtype, and regconfig) retain their original values. Other explicit store types still determine the parameter type and must accept the supplied value.
Canonical CRUD command text is cached by adapter instance, immutable mapping identity, operation shape, and batch row count. Per-call schema and adapter overrides do not enter the process-wide cache. This avoids repeated SQL string construction without permanently retaining open-ended override values.
Explicit Generated Keys
Use InsertWithExplicitGeneratedKeysAsync or BulkInsertAsync(..., includeAutoKey: true) only when importing values for keys normally generated by the database.
These operations do not advance or reset provider identity, sequence, or auto-increment state. The caller must maintain that state so later database-generated keys do not conflict with explicitly inserted values.
Dapper Global State and Type Handlers
On first use, DapperHelper calls InitializeTypeHandlers() to register Dapper.GuidTypeHandler if no GUID handler is already registered. This removes the built-in Guid and Guid? type mappings so Dapper uses the handler for parameters. These changes affect all Dapper calls in the process. The method can also be called explicitly before using ordinary Dapper APIs, or after resetting Dapper type handlers. Core CRUD operations do not scan assemblies or change other Dapper settings. Generated queries alias database columns back to CLR property names, so they do not require a global Dapper type map.
Dapper.GuidTypeHandler and Dapper.AssumeUtcDateTimeTypeHandler are optional helpers. Registering either through SqlMapper.AddTypeHandler changes Dapper process-wide state and remains the application's responsibility.
See DESIGN.md for the principles governing future changes.
| Product | Versions 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 is compatible. 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 is compatible. 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 is compatible. 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 is compatible. 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. |
-
.NETFramework 4.7.2
- Dapper (>= 2.1.89)
- FclEx.Core (>= 2.4.0)
-
.NETStandard 2.0
- Dapper (>= 2.1.89)
- FclEx.Core (>= 2.4.0)
-
net10.0
- Dapper (>= 2.1.89)
- FclEx.Core (>= 2.4.0)
-
net8.0
- Dapper (>= 2.1.89)
- FclEx.Core (>= 2.4.0)
-
net9.0
- Dapper (>= 2.1.89)
- FclEx.Core (>= 2.4.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.4.2 | 122 | 10/10/2026 |
| 2.4.1 | 101 | 10/8/2026 |
| 2.4.0 | 115 | 10/3/2026 |
| 2.3.9 | 105 | 9/27/2026 |
| 2.3.8 | 104 | 9/26/2026 |
| 2.3.7 | 97 | 9/24/2026 |
| 2.3.6 | 114 | 9/22/2026 |
| 2.3.5 | 105 | 9/22/2026 |
| 2.3.4 | 109 | 8/28/2026 |
| 2.3.3 | 122 | 8/22/2026 |
| 2.3.2 | 124 | 8/20/2026 |
| 2.3.1 | 114 | 8/19/2026 |
| 2.3.0 | 130 | 8/17/2026 |
| 2.2.9 | 134 | 7/18/2026 |
| 2.2.8 | 137 | 6/13/2026 |
| 2.2.6 | 131 | 6/8/2026 |
| 2.2.5 | 137 | 6/5/2026 |
| 2.2.4 | 136 | 6/5/2026 |
| 2.2.3 | 137 | 5/27/2026 |
| 2.2.2 | 132 | 5/26/2026 |