RepletoryLib.Data.Dapper 1.0.0

dotnet add package RepletoryLib.Data.Dapper --version 1.0.0
                    
NuGet\Install-Package RepletoryLib.Data.Dapper -Version 1.0.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="RepletoryLib.Data.Dapper" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RepletoryLib.Data.Dapper" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="RepletoryLib.Data.Dapper" />
                    
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 RepletoryLib.Data.Dapper --version 1.0.0
                    
#r "nuget: RepletoryLib.Data.Dapper, 1.0.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 RepletoryLib.Data.Dapper@1.0.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=RepletoryLib.Data.Dapper&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RepletoryLib.Data.Dapper&version=1.0.0
                    
Install as a Cake Tool

RepletoryLib.Data.Dapper

Dapper-based data access with a fluent SQL builder, JOINs, and bulk insert/update/delete support.

Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.

NuGet .NET 10 License: MIT


Overview

RepletoryLib.Data.Dapper provides lightweight, high-performance data access using Dapper. It includes a managed connection context (IDapperContext), a fluent SQL builder (ISqlBuilder) for constructing parameterized queries, and bulk insert extensions for efficient batch operations.

Use this package when you need raw SQL performance or when EF Core's overhead isn't justified for read-heavy or reporting scenarios.

Key Features

  • IDapperContext -- Managed database connection creation (sync and async)
  • ISqlBuilder -- Fluent API for building SELECT, INSERT, UPDATE, DELETE queries with JOINs and parameterized values
  • Bulk inserts -- Batch insert entities with configurable batch size
  • Bulk updates -- Batch update entities by primary key
  • Bulk deletes -- Batch delete rows by key values using WHERE IN
  • SQL Server support -- Built on Microsoft.Data.SqlClient

Installation

dotnet add package RepletoryLib.Data.Dapper

Or add to your .csproj:

<PackageReference Include="RepletoryLib.Data.Dapper" Version="1.0.0" />

Note: RepletoryLib packages are published to a local BaGet feed. See the main repository README for feed configuration.

Dependencies

Package Type
RepletoryLib.Common RepletoryLib
Dapper NuGet (2.1.66)
Microsoft.Data.SqlClient NuGet (5.2.2)

Prerequisites

  • SQL Server instance (local or remote)
  • Connection string configured in appsettings.json

Quick Start

using RepletoryLib.Data.Dapper;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRepletoryDapper(builder.Configuration);
{
  "RepletoryDapper": {
    "ConnectionString": "Server=localhost,1433;Database=MyApp;User Id=sa;Password=Repletory@123!;TrustServerCertificate=true",
    "CommandTimeoutSeconds": 30,
    "EnableQueryLogging": false
  }
}

Configuration

DapperOptions

Property Type Default Description
ConnectionString string "" SQL Server connection string
CommandTimeoutSeconds int 30 Command timeout in seconds
EnableQueryLogging bool false Log executed queries

Section name: "RepletoryDapper"


Usage Examples

Executing Queries with IDapperContext

using RepletoryLib.Data.Dapper.Interfaces;

public class ProductRepository
{
    private readonly IDapperContext _context;

    public ProductRepository(IDapperContext context) => _context = context;

    public async Task<Product?> GetByIdAsync(Guid id)
    {
        using var connection = await _context.CreateConnectionAsync();
        return await connection.QuerySingleOrDefaultAsync<Product>(
            "SELECT * FROM Products WHERE Id = @Id AND IsDeleted = 0",
            new { Id = id });
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        using var connection = await _context.CreateConnectionAsync();
        return await connection.QueryAsync<Product>(
            "SELECT * FROM Products WHERE IsDeleted = 0 ORDER BY CreatedAt DESC");
    }
}

Building Queries with ISqlBuilder

using RepletoryLib.Data.Dapper.Interfaces;

public class ReportRepository
{
    private readonly IDapperContext _context;
    private readonly ISqlBuilder _sqlBuilder;

    public ReportRepository(IDapperContext context, ISqlBuilder sqlBuilder)
    {
        _context = context;
        _sqlBuilder = sqlBuilder;
    }

    public async Task<IEnumerable<Product>> SearchAsync(string? name, decimal? minPrice, int page, int pageSize)
    {
        _sqlBuilder
            .Select("Products", "Id, Name, Sku, Price, StockQuantity, CreatedAt")
            .Where("IsDeleted = 0");

        if (!string.IsNullOrEmpty(name))
            _sqlBuilder.And("Name LIKE @Name").WithParameter("Name", $"%{name}%");

        if (minPrice.HasValue)
            _sqlBuilder.And("Price >= @MinPrice").WithParameter("MinPrice", minPrice.Value);

        _sqlBuilder
            .OrderBy("CreatedAt", descending: true)
            .Paginate((page - 1) * pageSize, pageSize);

        var sql = _sqlBuilder.Build();
        var parameters = _sqlBuilder.GetParameters();

        using var connection = await _context.CreateConnectionAsync();
        return await connection.QueryAsync<Product>(sql, parameters);
    }
}

SELECT with JOINs

// INNER JOIN
_sqlBuilder
    .Select("Orders o", "o.Id, o.TotalPrice, c.Name AS CustomerName")
    .Join("Customers c", "c.Id = o.CustomerId")
    .Where("o.TotalPrice > @MinTotal")
    .WithParameter("MinTotal", 100m)
    .OrderBy("o.TotalPrice", descending: true);

using var conn = await _context.CreateConnectionAsync();
var results = await conn.QueryAsync(sqlBuilder.Build(), sqlBuilder.GetParameters());

// LEFT JOIN (includes orders even without a customer)
_sqlBuilder.Reset();
_sqlBuilder
    .Select("Orders o", "o.Id, o.TotalPrice, c.Name AS CustomerName")
    .LeftJoin("Customers c", "c.Id = o.CustomerId")
    .Where("o.IsDeleted = 0")
    .OrderBy("o.CreatedAt", descending: true)
    .Paginate(0, 50);

// Multiple JOINs
_sqlBuilder.Reset();
_sqlBuilder
    .Select("OrderProducts op", "op.Quantity, p.Name AS ProductName, o.Id AS OrderId")
    .Join("Orders o", "o.Id = op.OrderId")
    .Join("Products p", "p.Id = op.ProductId")
    .Where("o.CustomerId = @CustomerId")
    .WithParameter("CustomerId", customerId);

INSERT, UPDATE, DELETE with SqlBuilder

// INSERT
_sqlBuilder
    .Insert("Products", "Id, Name, Sku, Price", "@Id, @Name, @Sku, @Price")
    .WithParameter("Id", Guid.NewGuid())
    .WithParameter("Name", "Widget")
    .WithParameter("Sku", "SKU-001")
    .WithParameter("Price", 29.99m);

using var conn = await _context.CreateConnectionAsync();
await conn.ExecuteAsync(_sqlBuilder.Build(), _sqlBuilder.GetParameters());

// UPDATE
_sqlBuilder.Reset();
_sqlBuilder
    .Update("Products", "Price = @Price, UpdatedAt = @Now")
    .Where("Sku = @Sku")
    .WithParameter("Price", 24.99m)
    .WithParameter("Now", DateTime.UtcNow)
    .WithParameter("Sku", "SKU-001");

await conn.ExecuteAsync(_sqlBuilder.Build(), _sqlBuilder.GetParameters());

// DELETE
_sqlBuilder.Reset();
_sqlBuilder
    .Delete("Products")
    .Where("Id = @Id")
    .WithParameter("Id", productId);

await conn.ExecuteAsync(_sqlBuilder.Build(), _sqlBuilder.GetParameters());

Bulk Insert

using RepletoryLib.Data.Dapper.Extensions;

var products = Enumerable.Range(1, 10000).Select(i => new Product
{
    Id = Guid.NewGuid(),
    Name = $"Product {i}",
    Sku = $"SKU-{i:D5}",
    Price = i * 1.99m,
    StockQuantity = 100
});

// Bulk insert via context (creates connection internally)
int inserted = await _context.BulkInsertAsync("Products", products, batchSize: 500);

// Or with an existing connection and transaction
using var connection = await _context.CreateConnectionAsync();
using var transaction = connection.BeginTransaction();
int rows = await connection.BulkInsertAsync("Products", products, transaction, batchSize: 1000);
transaction.Commit();

Bulk Update

using RepletoryLib.Data.Dapper.Extensions;

var updatedProducts = new[]
{
    new { Id = id1, Name = "Widget Pro", Price = 29.99m, StockQuantity = 50 },
    new { Id = id2, Name = "Gadget Plus", Price = 49.99m, StockQuantity = 25 },
    new { Id = id3, Name = "Gizmo Max", Price = 99.99m, StockQuantity = 10 },
};

// Bulk update via context (matches rows by "Id" column by default)
int updated = await _context.BulkUpdateAsync("Products", updatedProducts);

// Specify a different key column
int updated2 = await _context.BulkUpdateAsync("Products", updatedProducts, keyColumn: "Sku");

// With an existing connection and transaction
using var connection = await _context.CreateConnectionAsync();
using var transaction = connection.BeginTransaction();
int rows = await connection.BulkUpdateAsync("Products", updatedProducts, "Id", transaction);
transaction.Commit();

Bulk Delete

using RepletoryLib.Data.Dapper.Extensions;

// Delete by Guid IDs
var idsToDelete = new List<Guid> { id1, id2, id3 };
int deleted = await _context.BulkDeleteAsync("Products", idsToDelete);

// Delete by integer IDs
var intIds = new List<int> { 1, 2, 3, 4, 5 };
int deleted2 = await _context.BulkDeleteAsync("Logs", intIds);

// Specify a different key column
int deleted3 = await _context.BulkDeleteAsync("Products", skus, keyColumn: "Sku");

// With an existing connection and transaction
using var connection = await _context.CreateConnectionAsync();
using var transaction = connection.BeginTransaction();
int rows = await connection.BulkDeleteAsync("Products", idsToDelete, "Id", transaction);
transaction.Commit();

API Reference

IDapperContext

Method Returns Description
CreateConnection() IDbConnection Creates and opens a new database connection
CreateConnectionAsync(ct) Task<IDbConnection> Asynchronously creates and opens a connection

ISqlBuilder

Method Returns Description
Select(table, columns?) ISqlBuilder Begin SELECT (columns defaults to *)
Join(table, onCondition) ISqlBuilder Append INNER JOIN
LeftJoin(table, onCondition) ISqlBuilder Append LEFT JOIN
Insert(table, columns, values) ISqlBuilder Begin INSERT INTO
Update(table, setClause) ISqlBuilder Begin UPDATE ... SET
Delete(table) ISqlBuilder Begin DELETE FROM
Where(condition) ISqlBuilder Append WHERE clause
And(condition) ISqlBuilder Append AND condition
Or(condition) ISqlBuilder Append OR condition
OrderBy(column, descending?) ISqlBuilder Append ORDER BY
Paginate(offset, fetch) ISqlBuilder Append OFFSET/FETCH
WithParameter(name, value) ISqlBuilder Add named parameter
Build() string Generate the SQL string
GetParameters() IReadOnlyDictionary<string, object?> Get parameter dictionary
Reset() ISqlBuilder Clear builder state for reuse

Bulk Extensions

Method Extension On Description
BulkInsertAsync<T>(table, entities, batchSize?) IDapperContext / IDbConnection Multi-row INSERT with batching
BulkUpdateAsync<T>(table, entities, keyColumn?, batchSize?) IDapperContext / IDbConnection Batch UPDATE by key column
BulkDeleteAsync<TKey>(table, ids, keyColumn?, batchSize?) IDapperContext / IDbConnection DELETE WHERE IN with batching

Integration with Other RepletoryLib Packages

Package Relationship
RepletoryLib.Common Direct dependency
RepletoryLib.Data.EntityFramework Use EF Core for writes, Dapper for read-heavy queries
RepletoryLib.Data.Migrations Use EF migrations for schema, Dapper for queries
RepletoryLib.Utilities.Pagination Combine with Paginate() for consistent pagination

Testing

[Fact]
public void SqlBuilder_builds_select_with_pagination()
{
    var builder = new SqlBuilder();

    var sql = builder
        .Select("Products", "Id, Name, Price")
        .Where("IsDeleted = 0")
        .And("Price > @MinPrice")
        .WithParameter("MinPrice", 10m)
        .OrderBy("Price", descending: true)
        .Paginate(0, 20)
        .Build();

    sql.Should().Contain("SELECT Id, Name, Price FROM Products");
    sql.Should().Contain("WHERE IsDeleted = 0 AND Price > @MinPrice");
    sql.Should().Contain("ORDER BY Price DESC");
    sql.Should().Contain("OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY");
}

Troubleshooting

Issue Solution
SqlException: Login failed Verify ConnectionString in appsettings.json and ensure SQL Server is running
SqlBuilder generates wrong SQL Use Reset() between queries when reusing the same builder instance
Bulk insert is slow Reduce batchSize if transactions are timing out, or increase for throughput
Parameters not applied Ensure parameter names in SQL match the names passed to WithParameter (include @ in SQL, exclude in WithParameter)

License

This project is licensed under the MIT License.

Copyright (c) 2024-2026 Repletory.


For complete documentation, infrastructure setup, and configuration reference, see the RepletoryLib main repository.

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
1.0.0 140 3/2/2026