Nahmadov.DapperForge.SqlServer 2.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Nahmadov.DapperForge.SqlServer --version 2.2.0
                    
NuGet\Install-Package Nahmadov.DapperForge.SqlServer -Version 2.2.0
                    
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="Nahmadov.DapperForge.SqlServer" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nahmadov.DapperForge.SqlServer" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Nahmadov.DapperForge.SqlServer" />
                    
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 Nahmadov.DapperForge.SqlServer --version 2.2.0
                    
#r "nuget: Nahmadov.DapperForge.SqlServer, 2.2.0"
                    
#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 Nahmadov.DapperForge.SqlServer@2.2.0
                    
#: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=Nahmadov.DapperForge.SqlServer&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Nahmadov.DapperForge.SqlServer&version=2.2.0
                    
Install as a Cake Tool

Nahmadov.DapperForge

A lightweight, high-performance Dapper-based data access layer that provides an Entity Framework-style API surface while maintaining Dapper's speed and simplicity.


Key Philosophy

  • Immediate execution — No change tracker, no SaveChanges()
  • Explicit control — Direct SQL execution for maximum performance
  • Minimal allocations — Reduced memory pressure compared to EF Core
  • Database-agnostic — Pluggable SQL dialects (SQL Server, Oracle)
  • Fluent API — Familiar EF-like query and configuration syntax

NuGet Packages

Package Description
Nahmadov.DapperForge.Core Core library with abstractions
Nahmadov.DapperForge.SqlServer SQL Server dialect
Nahmadov.DapperForge.Oracle Oracle dialect

Quick Start

Installation

dotnet add package Nahmadov.DapperForge.Core
dotnet add package Nahmadov.DapperForge.SqlServer

Define Your Context

public class AppDbContext : DapperDbContext
{
    public DapperSet<Customer> Customers => Set<Customer>();
    public DapperSet<Order> Orders => Set<Order>();

    public AppDbContext(DapperDbContextOptions<AppDbContext> options)
        : base(options) { }

    protected override void OnModelCreating(DapperModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>(b =>
        {
            b.ToTable("Customers", "dbo");
            b.HasKey(c => c.Id);
            b.Property(c => c.Name).HasMaxLength(100).IsRequired();
        });
    }
}

Register in DI

services.AddDapperDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});

Use in Your Application

var activeCustomers = await _db.Customers
    .Where(c => c.IsActive)
    .OrderBy(c => c.Name)
    .ToListAsync();

var id = await _db.Customers.InsertAndGetIdAsync<int>(new Customer
{
    Name = "Alice",
    IsActive = true
});

await _db.Customers.DeleteByIdAsync(id);

Features

Fluent Query API

var customers = await db.Customers
    .Where(c => c.IsActive && c.Name.StartsWith("John"))
    .OrderBy(c => c.Name)
    .Skip(10)
    .Take(20)
    .ToListAsync();

Expression Translation

LINQ expressions are translated to parameterized SQL. Supported patterns:

Pattern Example Generated SQL
Comparisons c.Age > 18 WHERE a.[Age] > @Age
Null checks c.Email == null WHERE a.[Email] IS NULL
Booleans c.IsActive / !c.IsActive WHERE a.[IsActive] = 1 / = 0
String methods c.Name.StartsWith("J") WHERE a.[Name] LIKE @p0
Collection IN ids.Contains(c.Id) WHERE a.[Id] IN (1,2,3)
Logical ops &&, \|\|, ! AND, OR, NOT

Case-insensitive filtering is available via the ignoreCase parameter:

var customers = await db.Customers.WhereAsync(
    c => c.Name.Contains("john"), ignoreCase: true);
// WHERE LOWER(a.[Name]) LIKE LOWER(@p0)

Include / ThenInclude

Load related entities with an EF-style API:

var orders = await db.Orders
    .Include(o => o.Customer)
        .ThenInclude(c => c.Address)
    .Include(o => o.OrderItems)
        .ThenInclude(i => i.Product)
    .AsSplitQuery()
    .ToListAsync();

AsSingleQuery (default) — single SQL with JOINs; best for small result sets. AsSplitQuery — separate queries with IN clauses; avoids cartesian explosion for collections.

Bulk Insert

Insert large collections efficiently using automatic batching. Entities are split into batches that respect database parameter limits (2,100 for SQL Server, 32,767 for Oracle).

var products = GenerateProducts(5000);

var result = await db.Products.BulkInsertAsync(products);

Console.WriteLine($"Inserted {result.TotalAffected} rows in {result.BatchCount} batch(es), took {result.ElapsedTime}");

Configure with BulkInsertOptions:

var result = await db.Products.BulkInsertAsync(products, new BulkInsertOptions
{
    BatchSize = 200,
    CommandTimeout = 60,
    ValidateBeforeInsert = true   // default: true
});

Works with transactions:

var scope = await db.BeginTransactionScopeAsync();
try
{
    await db.Products.BulkInsertAsync(products, transaction: scope.Transaction);
    scope.Complete();
}
finally
{
    scope.Dispose();
}

Bulk Merge (Upsert)

Perform INSERT-or-UPDATE (MERGE) operations in bulk. Uses the SQL MERGE statement under the hood.

var result = await db.Products.BulkMergeAsync(products);

Console.WriteLine($"Affected {result.TotalAffected} rows (inserted: {result.RowsInserted}, updated: {result.RowsUpdated})");

Control merge behavior with BulkMergeOptions:

var result = await db.Products.BulkMergeAsync(products, new BulkMergeOptions
{
    MatchColumns = new[] { "TenantId", "ProductCode" },  // columns used for matching
    Mode = MergeMode.InsertOrUpdate,  // or InsertOnly, UpdateOnly
    BatchSize = 100,
    ValidateBeforeMerge = true
});

Shorthand for custom match columns:

var result = await db.Products.BulkMergeAsync(
    products,
    matchColumns: new[] { "TenantId", "ProductCode" });

Merge Modes:

Mode Behavior
InsertOrUpdate Insert new rows, update existing (default)
InsertOnly Only insert rows that don't exist
UpdateOnly Only update existing rows

Transactions

var scope = await db.BeginTransactionScopeAsync();
try
{
    var customerId = await db.Customers.InsertAndGetIdAsync<int>(customer, scope.Transaction);
    order.CustomerId = customerId;
    await db.Orders.InsertAsync(order, scope.Transaction);
    scope.Complete();
}
finally
{
    scope.Dispose();
}

Validation

Entities are validated before insert/update using [Required], [MaxLength], [StringLength] attributes or fluent configuration.

modelBuilder.Entity<Customer>(b =>
{
    b.Property(c => c.Name).IsRequired().HasMaxLength(100);
});

// Throws DapperValidationException on invalid data
await db.Customers.InsertAsync(customer);

Read-only entities and properties are protected from mutation.

Automatic Retry

Transient database failures (timeouts, deadlocks, connection drops) are automatically retried with exponential backoff.


Entity Configuration

Fluent API

public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.ToTable("Customers", "dbo");
        builder.HasKey(c => c.Id);
        builder.Property(c => c.Name).HasColumnName("FullName").HasMaxLength(100).IsRequired();
        builder.Property(c => c.CreatedAt).IsReadOnly();
    }
}

Apply configurations:

protected override void OnModelCreating(DapperModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}

Data Annotations

[Table("Customers", Schema = "dbo")]
public class Customer
{
    [Key]
    public int Id { get; set; }

    [Required, MaxLength(100), Column("FullName")]
    public string Name { get; set; }

    [ReadOnly]
    public DateTime CreatedAt { get; set; }
}

Key Detection

Keys are resolved in order: [Key] attribute, fluent HasKey(), property named Id, or property named {TypeName}Id.


Database Dialects

Feature SQL Server Oracle
Identifier quoting [Table] "Table"
Parameter prefix @p0 :p0
Auto-generated keys SCOPE_IDENTITY() RETURNING ... INTO
Max parameters 2,100 32,767
Default schema dbo (none)
// SQL Server
options.UseSqlServer(connectionString);

// Oracle
options.UseOracle(connectionString);

CRUD API Reference

Operation Method
Get all GetAllAsync()
Find by ID FindAsync(id)
Filter WhereAsync(predicate) / .Where(pred).ToListAsync()
First FirstOrDefaultAsync(predicate)
Any AnyAsync(predicate)
Count CountAsync(predicate)
Insert InsertAsync(entity)
Insert + get ID InsertAndGetIdAsync<TKey>(entity)
Update UpdateAsync(entity)
Delete DeleteAsync(entity) / DeleteByIdAsync(id)
Bulk insert BulkInsertAsync(entities, options?)
Bulk merge BulkMergeAsync(entities, options?)

Architecture

Application Layer
       │
   DapperDbContext  (connection management, entity sets)
       │
  ┌────┼────┐
Query  Mutation  Config
  │    │         │
  SQL Generation Layer  (SqlGenerator, PredicateVisitor)
       │
  SQL Dialect Layer  (SqlServer, Oracle)
       │
  Dapper + ADO.NET

Design patterns: Repository (DapperSet<T>), Unit of Work (TransactionScope), Builder (model configuration), Strategy (ISqlDialect), Facade, Factory, Template Method.


Performance

  • No change tracker — minimal memory overhead
  • SQL statements pre-generated and cached
  • Expression compilation cached (thread-safe LRU, max 1,000 entries)
  • Identity cache prevents duplicate instances during Include operations
  • Parameterized queries enable database query plan caching
  • Bulk operations use automatic batching for optimal throughput

Migration from EF Core

EF Core DapperForge
DbContext DapperDbContext
DbSet<T> DapperSet<T>
Where(pred).ToListAsync() WhereAsync(pred) or .Where(pred).ToListAsync()
Add() + SaveChangesAsync() InsertAsync()
Update() + SaveChangesAsync() UpdateAsync()
Remove() + SaveChangesAsync() DeleteAsync()
Include() / ThenInclude() Same API
AsNoTracking() N/A (never tracks)
SaveChangesAsync() N/A (immediate execution)

Limitations

  1. Single-column keys only — no composite key support
  2. Limited LINQ — no aggregations, grouping, or complex joins in the fluent API (use raw SQL)
  3. No lazy loading — must use Include
  4. No change tracking
  5. No migrations — DDL managed externally
  6. Expression translator covers common filter patterns only

Project Structure

DapperToolkit/
├── src/
│   ├── Nahmadov.DapperForge.Core/       # Core library
│   ├── Nahmadov.DapperForge.SqlServer/  # SQL Server dialect
│   └── Nahmadov.DapperForge.Oracle/     # Oracle dialect
├── tests/
│   └── Nahmadov.DapperForge.UnitTests/  # Unit tests
├── samples/
│   └── ConnectionSample/                # Usage samples
└── docs/                                # Detailed documentation

Testing

dotnet test

Sample App

cd samples/ConnectionSample
dotnet run

Configure via user secrets (FullSample):

  • Provider: SqlServer (default) or Oracle
  • SqlServerConnectionString / OracleConnectionString

Documentation

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.3.0 77 3/16/2026
2.2.0 98 2/9/2026
2.1.3 101 1/16/2026
2.1.2 96 1/16/2026
2.1.1 100 1/15/2026
2.1.0 100 1/14/2026
2.0.0 99 1/7/2026
1.1.0 98 1/7/2026
1.0.1 445 12/9/2025