Javxp.DapperHelper
1.0.1
dotnet add package Javxp.DapperHelper --version 1.0.1
NuGet\Install-Package Javxp.DapperHelper -Version 1.0.1
<PackageReference Include="Javxp.DapperHelper" Version="1.0.1" />
<PackageVersion Include="Javxp.DapperHelper" Version="1.0.1" />
<PackageReference Include="Javxp.DapperHelper" />
paket add Javxp.DapperHelper --version 1.0.1
#r "nuget: Javxp.DapperHelper, 1.0.1"
#:package Javxp.DapperHelper@1.0.1
#addin nuget:?package=Javxp.DapperHelper&version=1.0.1
#tool nuget:?package=Javxp.DapperHelper&version=1.0.1
Javxp.DapperHelper
项目概述
Javxp.DapperHelper 是一个基于 Dapper 的高性能存储过程驱动数据访问引擎,面向企业级 .NET 应用。它封装了 Dapper 的底层操作,以存储过程为唯一调用入口,提供类型安全、极简调用的数据访问体验。
核心能力:
- 全异步 API —— 所有数据库操作均为异步方法,支持
CancellationToken取消 - 多结果集自动聚合 ——
QueryMultipleAsync<TResult>通过 Expression Tree 编译缓存,实现聚合 DTO 的零反射自动映射 - 流式查询 ——
QueryStreamAsync基于IAsyncEnumerable<T>逐行产出,百万级数据内存占用极低 - 分页查询 —— 自动绑定
OUTPUT参数,开箱即用的分页支持 - SQL Server 批量插入 ——
BulkCopyAsync封装SqlBulkCopy,万级/十万级数据秒级写入 - TVP 参数构建 ——
ToDataTable扩展方法一键构建 Table-Valued Parameter - 聚合关联 ——
AggregateHelper将父子集合按外键快速关联回填 - 依赖注入集成 —— 一行代码完成服务注册
运行环境
| 项目 | 说明 |
|---|---|
| 开发语言 | C# 13 |
| 目标框架 | .NET 10.0 |
| 核心依赖 | Dapper 2.1.72 |
| 数据库驱动 | Microsoft.Data.SqlClient 7.0.1 |
| DI 抽象 | Microsoft.Extensions.DependencyInjection.Abstractions 10.0.0 |
| 数据库支持 | SQL Server(完全支持),架构预留 MySQL / PostgreSQL 扩展 |
| 编译器依赖 | Microsoft.CodeAnalysis.CSharp 5.3.0(源生成器支持) |
集成示例
1. 依赖注入注册
using Javxp.DapperHelper;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// 一行代码注册所有服务
services.JavxpDapperHelper(DBProvider.SQLServer, "Server=.;Database=MyDb;Trusted_Connection=true;");
var provider = services.BuildServiceProvider();
var db = provider.GetRequiredService<IDBService>();
内部注册:
IDbConnectionFactory(Singleton)+IDBService(Scoped),保证同一 Scope 内共享连接。
2. 单条查询(QuerySingleAsync)
// DTO 定义
public class OrderDto
{
public long OrderId { get; set; }
public string OrderNo { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
public decimal TotalAmount { get; set; }
public DateTime CreatedAt { get; set; }
}
// 查询单条记录(无匹配返回 null)
OrderDto? order = await db.QuerySingleAsync<OrderDto>(
"usp_GetOrderById",
new { OrderId = 1001 });
// 带事务查询
OrderDto? orderInTx = await db.QuerySingleAsync<OrderDto>(
"usp_GetOrderById",
new { OrderId = 1001 },
transaction: currentTransaction);
3. 列表查询(QueryListAsync)
// 查询多条记录,返回 IReadOnlyList<T>
IReadOnlyList<OrderDto> orders = await db.QueryListAsync<OrderDto>(
"usp_GetOrdersByCustomer",
new { CustomerId = 50 });
// 无参数查询全部
IReadOnlyList<OrderDto> allOrders = await db.QueryListAsync<OrderDto>(
"usp_GetAllOrders");
4. 多结果集自动聚合(QueryMultipleAsync<TResult>)
存储过程返回多个结果集时,QueryMultipleAsync<TResult> 自动按属性声明顺序将结果集映射到聚合 DTO,无需手动逐个读取。
// 聚合 DTO —— 属性声明顺序必须与存储过程返回的结果集顺序一致
public class OrderAggregate
{
// 第1个结果集 → 单个订单(单对象属性自动取第一条记录)
public OrderDto? Order { get; set; }
// 第2个结果集 → 订单明细列表(集合属性绑定整个结果集)
public IReadOnlyList<OrderItemDto> Items { get; set; } = [];
// 第3个结果集 → 操作日志
public IReadOnlyList<OrderLogDto> Logs { get; set; } = [];
}
public class OrderItemDto
{
public long ItemId { get; set; }
public string ProductName { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
public class OrderLogDto
{
public long LogId { get; set; }
public string Action { get; set; } = string.Empty;
public DateTime OperatedAt { get; set; }
}
// 一次调用,自动聚合所有结果集
OrderAggregate result = await db.QueryMultipleAsync<OrderAggregate>(
"usp_GetOrderDetail",
new { OrderId = 1001 });
// 直接使用:result.Order、result.Items、result.Logs 均已填充
映射规则:
- 集合属性(
IReadOnlyList<T>、List<T>、IEnumerable<T>等)→ 绑定整个结果集 - 单对象属性(
class类型,非string)→ 自动取结果集第一条记录(FirstOrDefault) - 属性声明顺序严格对应存储过程返回的结果集顺序
5. 多结果集手动读取(QueryMultipleAsync)
需要灵活控制读取逻辑时,使用非泛型 QueryMultipleAsync 获取 IDataSet 手动按序读取。
// 必须 await using 确保连接释放
await using var dataSet = await db.QueryMultipleAsync(
"usp_GetOrderDetail",
new { OrderId = 1001 });
// 按结果集返回顺序依次读取
var order = await dataSet.ReadAsync<OrderDto>();
var items = await dataSet.ReadAsync<OrderItemDto>();
// 也可使用自定义映射函数读取
var customItems = await dataSet.ReadAsync<OrderItemDto>(
reader => reader.ReadAsync<OrderItemDto>());
注意:
ReadAsync调用顺序必须与数据库返回的结果集顺序一致,否则映射错乱。
6. 流式查询(QueryStreamAsync)
适合百万级大数据量场景,使用 IAsyncEnumerable<T> 逐行产出,内存占用极低。
// 流式遍历大结果集
await foreach (var order in db.QueryStreamAsync<OrderDto>(
"usp_ExportOrders",
new { Year = 2025 },
cancellationToken: ct))
{
// 逐行处理,不会一次性加载所有数据到内存
await ExportToFile(order);
}
实现原理:基于 DbDataReader + Dapper GetRowParser<T> 逐行读取,不缓存全部结果。
7. 分页查询(QueryToPageAsync)
// 构建分页请求
var page = new PageRequest(PageIndex: 1, PageSize: 20);
// 执行分页查询
PageResponse<OrderDto> result = await db.QueryToPageAsync<OrderDto>(
"usp_GetOrdersByPage",
page,
new { Status = "Completed" });
// 使用分页结果
Console.WriteLine($"总记录数: {result.TotalCount}");
Console.WriteLine($"总页数: {result.TotalPages}");
Console.WriteLine($"是否有下一页: {result.HasNextPage}");
Console.WriteLine($"是否有上一页: {result.HasPreviousPage}");
foreach (var order in result.Items)
{
Console.WriteLine($"{order.OrderNo} - {order.TotalAmount:C}");
}
存储过程参数约定(必须按此约定定义 OUTPUT 参数):
| 参数名 | 方向 | 说明 |
|---|---|---|
@PageSize |
IN/OUT | 每页记录数 |
@PageIndex |
IN/OUT | 页码(从 1 开始) |
@PageTotal |
OUT | 总页数 |
@RecordTotal |
OUT | 总记录数 |
8. 执行存储过程(ExecuteAsync)
// 执行写入操作,返回受影响行数
int affected = await db.ExecuteAsync(
"usp_UpdateOrderStatus",
new { OrderId = 1001, Status = "Shipped" });
// 带事务执行
int affectedInTx = await db.ExecuteAsync(
"usp_CancelOrder",
new { OrderId = 1001 },
transaction: currentTransaction);
// 执行标量查询,返回单个值
long newId = await db.ExecuteScalarAsync<long>(
"usp_CreateOrder",
new { CustomerId = 50, TotalAmount = 199.99m });
9. 批量插入(BulkCopyAsync)
基于 SqlBulkCopy 的高性能批量写入,适合万级/十万级数据导入。
public class OrderItemEntity
{
public long OrderId { get; set; }
public string ProductCode { get; set; } = string.Empty;
public string ProductName { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
// 构建批量数据
var items = new List<OrderItemEntity>
{
new() { OrderId = 1001, ProductCode = "P001", ProductName = "商品A", Quantity = 2, UnitPrice = 99.9m },
new() { OrderId = 1001, ProductCode = "P002", ProductName = "商品B", Quantity = 5, UnitPrice = 49.5m },
// ... 更多数据
};
// 执行批量插入(默认每批 5000 行)
bool success = await db.BulkCopyAsync(
"OrderItems", // 目标表名
items.AsReadOnly(), // 数据集合
batchSize: 5000); // 每批写入行数
// 自定义批次大小
bool success2 = await db.BulkCopyAsync(
"OrderItems",
items.AsReadOnly(),
batchSize: 10000,
cancellationToken: ct);
注意:
BulkCopyAsync仅支持 SQL Server。数据实体的属性名必须与目标表列名一致,自动按名称映射。
10. TVP 参数构建(ToDataTable)
将集合转换为 DataTable,用于 SQL Server Table-Valued Parameter(表值参数)。
简单值类型集合 → 单列 DataTable:
// int[] 转单列 DataTable
int[] orderIds = [1001, 1002, 1003];
DataTable idTable = orderIds.ToDataTable("OrderId");
// string[] 转单列 DataTable
string[] productCodes = ["P001", "P002", "P003"];
DataTable codeTable = productCodes.ToDataTable("ProductCode");
// List<long> 转单列 DataTable
var ids = new List<long> { 1001, 1002, 1003 };
DataTable longTable = ids.ToDataTable("Id");
// 作为 TVP 参数传递给存储过程
var orders = await db.QueryListAsync<OrderDto>(
"usp_GetOrdersByIds",
new { Ids = idTable.AsTableValuedParameter("dbo.IntTableType") });
复杂对象集合 → 多列 DataTable:
public class OrderItemParam
{
public long OrderId { get; set; }
public string ProductCode { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
var itemParams = new List<OrderItemParam>
{
new() { OrderId = 1001, ProductCode = "P001", Quantity = 2, UnitPrice = 99.9m },
new() { OrderId = 1001, ProductCode = "P002", Quantity = 5, UnitPrice = 49.5m },
};
// 无参 ToDataTable() 自动根据属性生成多列
DataTable tvpTable = itemParams.ToDataTable();
// 作为 TVP 参数传递
int affected = await db.ExecuteAsync(
"usp_BatchInsertOrderItems",
new { Items = tvpTable.AsTableValuedParameter("dbo.OrderItemType") });
内部使用
DataTableBuilder的 Expression Tree 缓存机制,避免反复反射,大批量转换性能优异。
11. 聚合关联(AggregateHelper)
将独立查询的父子集合按外键快速关联回填,适用于 QueryMultipleAsync<TResult> 无法覆盖的灵活场景。
单子集合关联:
using Javxp.DapperHelper.Core;
public class OrderWithItems
{
public long OrderId { get; set; }
public string OrderNo { get; set; } = string.Empty;
public IReadOnlyList<OrderItemDto> Items { get; set; } = [];
}
// 独立查询父集合和子集合
var orders = await db.QueryListAsync<OrderWithItems>("usp_GetOrders");
var items = await db.QueryListAsync<OrderItemDto>("usp_GetOrderItems");
// 按外键关联回填
AggregateHelper.AggregatePooled<OrderWithItems, OrderItemDto, long>(
parents: orders,
children: items,
parentKeySelector: o => o.OrderId, // 父表外键
childKeySelector: i => i.OrderId, // 子表外键
assignExpr: o => o.Items); // 赋值目标属性
// 现在 orders 中每个 OrderWithItems.Items 已填充对应的子记录
多子集合关联:
public class OrderFullView
{
public long OrderId { get; set; }
public string OrderNo { get; set; } = string.Empty;
public IReadOnlyList<OrderItemDto> Items { get; set; } = [];
public IReadOnlyList<OrderLogDto> Logs { get; set; } = [];
}
var orders = await db.QueryListAsync<OrderFullView>("usp_GetOrders");
var items = await db.QueryListAsync<OrderItemDto>("usp_GetOrderItems");
var logs = await db.QueryListAsync<OrderLogDto>("usp_GetOrderLogs");
// 一次调用关联多个子集合
AggregateHelper.AggregateMultiPooled<OrderFullView, long>(
parents: orders,
builder: builder =>
{
builder.Add(items, o => o.OrderId, i => i.OrderId, o => o.Items);
builder.Add(logs, o => o.OrderId, l => l.OrderId, o => o.Logs);
});
// 大数据量时可启用并行(父集合 > 64 条时自动并行)
AggregateHelper.AggregateMultiPooled<OrderFullView, long>(
parents: orders,
builder: builder =>
{
builder.Add(items, o => o.OrderId, i => i.OrderId, o => o.Items);
builder.Add(logs, o => o.OrderId, l => l.OrderId, o => o.Logs);
},
parallel: true);
性能特性:
- 内部使用
Dictionary<TKey, List<TChild>>构建索引,时间复杂度 O(N+M) - Setter 委托通过 Expression Tree 编译并缓存,热路径零反射
parallel: true时,父集合超过 64 条记录自动使用Parallel.ForEach并行回填
模块职责:
| 模块 | 职责 |
|---|---|
| Abstractions | 定义业务层依赖的接口和模型,业务层零耦合实现细节 |
| Core | 实现数据库操作核心逻辑,管理连接生命周期和事务复用 |
| Mapping | 自动聚合映射引擎,首次调用编译 Expression Tree,后续 O(1) 命中缓存 |
| Extensions | 提供开发体验增强:DI 注册、批量插入、TVP 构建 |
核心设计原理
连接管理策略
DBService 根据是否存在事务自动选择连接获取方式:
- 无事务:通过
IDbConnectionFactory.CreateOpenConnectionAsync()创建新连接,操作完成后自动释放(await using) - 有事务:复用
DbTransaction.Connection,保证同一事务内所有操作共享连接,支持原子提交/回滚 - 多结果集场景:
QueryMultipleAsync始终创建独立连接,由DapperDataSet实现IAsyncDisposable统一管理GridReader和连接的释放顺序
Expression Tree 编译缓存
项目在三个核心位置使用 Expression Tree 编译缓存,消除热路径反射开销:
| 组件 | 编译内容 | 缓存策略 |
|---|---|---|
| MappingPlan | 聚合 DTO 属性的 Setter 委托 + Reader 委托 |
ConcurrentDictionary<Type, MappingStep[]>,按 TResult 类型缓存 |
| DataTableBuilder | 数据实体的属性 Getter 委托 |
ConcurrentDictionary<Type, PropertyAccessor[]>,按数据类型缓存 |
| AggregateHelper | 父对象属性的 Setter 委托 |
ConcurrentDictionary<string, object>,按类型全名 + 表达式字符串缓存 |
所有编译均为首次调用触发,后续 O(1) 命中缓存,运行时循环体内仅调用预编译委托,热路径零反射。
多结果集映射机制
QueryMultipleAsync<TResult> 的自动聚合映射流程:
- 元数据构建(仅首次):
MappingPlan.GetOrCreateSteps<TResult>()扫描TResult所有可写属性,区分为集合属性和单对象属性,为每个属性编译Setter和Reader委托,缓存为MappingStep[] - 数据读取:调用非泛型
QueryMultipleAsync获取IDataSet - 按序映射:遍历缓存的
MappingStep[],按属性声明顺序依次调用Reader从IDataSet读取对应结果集 - 类型自适应赋值:集合属性直接赋值整个结果集;单对象属性自动取
FirstOrDefault
这种设计将所有反射操作(GetProperty、MakeGenericMethod、Invoke)集中在构建阶段一次性完成,运行时仅执行编译后的委托调用。
| Product | Versions 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. |
-
net10.0
- Dapper (>= 2.1.72)
- Microsoft.Data.SqlClient (>= 7.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.