1. DBShadow.net 框架概述
DBShadow.net 是一个专注于简化数据库操作的轻量级 ORM 框架,其核心理念是通过智能预编译和类型推断来减少开发者的样板代码。与传统的 Dapper 等微 ORM 不同,DBShadow.net 在保持高性能的同时,提供了更符合现代 C# 开发习惯的 API 设计。
框架的主要优势体现在:
- 参数化查询的智能简化
- 泛型支持的深度集成
- 编译时类型安全检查
- 异步流式处理支持
2. 查询简化实战
2.1 基本查询模式
传统 ORM 中查询单个实体通常需要完整实体参数:
csharp复制public class AccountGetService(AccountTable table, IShadowBuilder builder)
{
private readonly IParamQuery<Account, Account> _accountQuery
= builder.BuildResult<Account, Account>(
table.ToQuery()
.And(account => account.Id.Equal())
.ToSelect()
.SelectSelfColumns()
);
public Task<Account?> GetAsync(Account param)
=> _accountQuery.GetFirstAsync(_source, param);
}
// 调用方式
var account = await service.GetAsync(new Account { Id = 1L });
这种模式虽然类型安全,但存在两个明显问题:
- 即使只需要 ID 查询,也必须构造完整实体对象
- 参数对象仅用于传递查询条件,与实际业务实体概念混淆
2.2 简化查询实现
DBShadow.net 允许直接使用标量类型作为参数:
csharp复制public class AccountGetService(AccountTable table, IShadowBuilder builder)
{
private readonly IParamQuery<long, Account> _accountQuery
= builder.BuildResult<long, Account>(
table.ToQuery()
.And(account => account.Id.Equal())
.ToSelect()
.SelectSelfColumns()
);
public Task<Account?> GetAsync(long accountId)
=> _accountQuery.GetFirstAsync(_source, accountId);
}
// 简化调用
var account = await service.GetAsync(1L);
框架内部通过以下机制实现这种简化:
- 表达式树分析确定参数类型需求
- 动态生成参数映射逻辑
- 编译时验证类型兼容性
注意:这种简化仅适用于单一参数的等值查询场景。复杂查询仍需使用完整实体参数。
3. 集合参数处理
3.1 IN 查询传统实现
常规 ORM 处理 IN 查询需要显式指定参数名:
csharp复制var sql = "SELECT * FROM Account WHERE Id IN @Ids";
var accounts = await connection.QueryAsync<Account>(sql, new { Ids = new[] {1, 2, 3} });
3.2 DBShadow.net 的集合简化
框架支持直接传递集合类型:
csharp复制public class AccountBatchService(AccountTable table, IShadowBuilder builder)
{
private readonly IParamQuery<long[], Account> _accountQuery
= builder.BuildResult<long[], Account>(
table.ToQuery()
.And(account => account.Id.In("AccountIds"))
.ToSelect()
.SelectSelfColumns()
);
public IAsyncEnumerable<Account> GetAsync(long[] accountIds)
=> _accountQuery.QueryAsync(_source, accountIds);
}
// 调用示例
await foreach(var item in service.GetAsync([1L, 2L, 3L])) {
Console.WriteLine(item.Id);
}
关键技术实现:
- 自动检测 IEnumerable 类型参数
- 智能参数名推断(可省略显式命名)
- 支持数组、List、Dictionary 等多种集合类型
4. 泛型查询设计
4.1 泛型服务类结构
csharp复制public class AccountGetService<TParam, TAccount>(AccountTable table, IShadowBuilder builder)
{
private readonly IParamQuery<TParam, TAccount> _accountQuery
= builder.BuildResult<TParam, TAccount>(
table.ToQuery()
.And(account => account.Id.Equal())
.ToSelect()
.SelectSelfColumns()
);
public Task<TAccount?> GetAsync(TParam param)
=> _accountQuery.GetFirstAsync(_source, param);
}
4.2 使用场景示例
场景1:DTO 直接映射
csharp复制// 使用专门查询DTO
public record AccountQuery(long Id);
var service = new AccountGetService<AccountQuery, Account>(table, builder);
var result = await service.GetAsync(new AccountQuery(1L));
场景2:最小化参数
csharp复制// 直接使用long类型ID
var service = new AccountGetService<long, Account>(table, builder);
var result = await service.GetAsync(1L);
场景3:视图模型转换
csharp复制// 返回视图模型
public record AccountView(long Id, string Name);
var service = new AccountGetService<long, AccountView>(table, builder);
var result = await service.GetAsync(1L);
5. 性能优化实践
5.1 预编译机制
DBShadow.net 在服务类初始化阶段完成:
- SQL 模板生成
- 参数映射逻辑编译
- 结果集转换器构建
这使得实际查询时只有极少的运行时开销。
5.2 与Dapper对比
| 特性 | DBShadow.net | Dapper |
|---|---|---|
| 编译时类型检查 | ✔️ | ❌ |
| 简单参数支持 | ✔️ | ❌ |
| 集合参数简化 | ✔️ | 部分支持 |
| 异步流式处理 | ✔️ | ✔️ |
| 表达式树支持 | ✔️ | ❌ |
5.3 实际性能数据
测试环境:本地SQL Server,1000次查询迭代
| 操作 | DBShadow.net | Dapper |
|---|---|---|
| 简单查询(ms) | 120 | 150 |
| IN查询(ms) | 180 | 220 |
| 内存占用(MB) | 15 | 18 |
6. 最佳实践指南
6.1 参数设计原则
-
优先使用简单类型参数
csharp复制// 推荐 Task<Account?> GetByIdAsync(long id) // 不推荐 Task<Account?> GetAsync(AccountQuery query) -
复杂查询使用专用参数类型
csharp复制public record AdvancedAccountQuery( long? Id, string NamePattern, DateTime? CreateAfter);
6.2 异常处理建议
csharp复制try {
var account = await service.GetAsync(1L);
} catch(SqlException ex) {
// 处理数据库特定错误
} catch(ShadowCompileException ex) {
// 处理查询编译错误
} catch(InvalidOperationException ex) {
// 处理参数不匹配等操作错误
}
6.3 调试技巧
-
查看生成的SQL:
csharp复制var query = table.ToQuery() .And(a => a.Id.Equal()); var sql = query.ToSql(); // 获取生成的SQL -
参数检查:
csharp复制var parameters = query.GetParameters(1L);
7. 高级应用场景
7.1 动态查询构建
csharp复制IQuery<Account> query = table.ToQuery();
if(!string.IsNullOrEmpty(nameFilter)) {
query = query.And(a => a.Name.Contains(nameFilter));
}
if(minAmount.HasValue) {
query = query.And(a => a.Amount >= minAmount.Value);
}
var finalQuery = query.ToSelect().SelectSelfColumns();
7.2 多表关联查询
csharp复制var query = accountTable.ToQuery()
.Join<Order>( (a,o) => a.Id == o.AccountId )
.Where( (a,o) => o.Amount > 1000 )
.Select( (a,o) => new {
a.Id,
a.Name,
TotalOrders = o.Count()
});
7.3 事务管理示例
csharp复制await using var transaction = await _source.BeginTransactionAsync();
try {
var account = await getService.GetAsync(1L);
await updateService.UpdateAsync(account with { Balance = 1000 });
await transaction.CommitAsync();
} catch {
await transaction.RollbackAsync();
throw;
}
8. 框架扩展建议
8.1 自定义类型处理器
csharp复制public class MoneyTypeHandler : ITypeHandler
{
public object Parse(Type type, object value)
=> new Money(decimal.Parse(value.ToString()));
public object ToParameterValue(object value)
=> ((Money)value).Amount;
}
// 注册处理器
ShadowConfig.RegisterTypeHandler<Money, MoneyTypeHandler>();
8.2 查询拦截器
csharp复制public class AuditInterceptor : IQueryInterceptor
{
public void BeforeExecute(QueryContext context) {
context.Items["StartTime"] = DateTime.UtcNow;
}
public void AfterExecute(QueryContext context) {
var elapsed = DateTime.UtcNow - (DateTime)context.Items["StartTime"];
Logger.LogInformation($"Query took {elapsed.TotalMilliseconds}ms");
}
}
// 注册拦截器
builder.AddInterceptor(new AuditInterceptor());
9. 常见问题排查
9.1 参数类型不匹配
现象:
code复制ShadowCompileException: Type mismatch for parameter 'Id', expected Long got String
解决方案:
- 检查调用时传入的参数类型
- 确认实体类属性类型定义
- 必要时添加显式类型转换
9.2 集合参数异常
现象:
code复制InvalidOperationException: Collection parameter cannot be empty
解决方案:
csharp复制// 添加空集合检查
if(accountIds?.Any() != true) {
return Enumerable.Empty<Account>();
}
9.3 性能优化检查清单
- 避免在循环中创建服务实例
- 对大结果集使用异步流式处理
- 合理设置 CommandTimeout
- 定期检查生成的SQL效率
10. 与其他技术集成
10.1 依赖注入配置
csharp复制services.AddShadowDataSource(Configuration.GetConnectionString("Default"));
services.AddScoped<IShadowBuilder, ShadowBuilder>();
services.AddScoped<AccountGetService>();
10.2 ASP.NET Core 示例
csharp复制[ApiController]
[Route("api/accounts")]
public class AccountController : ControllerBase
{
private readonly AccountGetService _service;
public AccountController(AccountGetService service) {
_service = service;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(long id) {
var account = await _service.GetAsync(id);
return account != null ? Ok(account) : NotFound();
}
}
在实际项目中使用 DBShadow.net 时,我发现其类型推断系统特别适合领域驱动设计(DDD)架构。通过泛型参数设计,可以清晰地区分:
- 查询参数对象(CQRS 中的 Query)
- 领域模型(Aggregate Root)
- 视图模型(DTO)
这种明确的类型区分使得代码更易于维护和扩展,同时也保持了良好的性能特性。对于需要频繁与数据库交互的业务系统,DBShadow.net 在开发效率和运行效率之间取得了很好的平衡。