惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
爱范儿
爱范儿
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
罗磊的独立博客
D
Docker
美团技术团队
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
月光博客
月光博客
P
Proofpoint News Feed
博客园 - 聂微东
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
H
Help Net Security
T
Tailwind CSS Blog
S
Securelist
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
Y
Y Combinator Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - Franky
S
Security @ Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
U
Unit 42
S
SegmentFault 最新的问题
Project Zero
Project Zero
P
Palo Alto Networks Blog
T
Threat Research - Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
F
Full Disclosure
P
Privacy & Cybersecurity Law Blog
J
Java Code Geeks
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
Recent Commits to openclaw:main
Recent Commits to openclaw:main
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
V
Vulnerabilities – Threatpost
S
Schneier on Security

博客园 - xiangji

开源完美模块组件化可扩展的Xml解析器Hand.ParseXml Roslyn语法的模式匹配之EasySyntax增加模式匹配支持 SourceGenerator之扑风捉影 开源项目PocoEmit.Mapper重构之扑风捉影 .NET源码生成器基于partial范式开发和nuget打包 SourceGenerator之partial范式及测试 .NET源码生成器之SyntaxTree踩坑 .NET源码生成器使用SyntaxTree生成代码及简化语法 DBShadow.net之依赖注入 DBShadow.net之性能优化的坎坷路 DBShadow横空出世,Dapper.net的天花板盖不住了 Aspire+.NET10+手搓线程池打造抓不死的云应用 鸡肋的TaskFactory是时候抛弃了 重构《手搓》TaskFactory带你更安全的起飞 异步"伪线程"重构《手搓》线程池,支持任务清退 《手搓》线程池优化的追求 《手搓》TaskFactory带你安全的起飞 《手搓》线程池 致敬1024,《手搓》轻量级EventBus PocoEmit遥遥领先于AutoMapper之打通充血模型的任督二脉 PocoEmit遥遥领先于AutoMapper之循环引用 微软.net表达式编译居然有bug? 如何使用PocoEmit.Mapper替代AutoMapper
DBShadow.net之化繁为简
xiangji · 2026-01-25 · via 博客园 - xiangji

一、简化查询

1. 先看一下查询的例子

/// <summary>
/// 账户获取服务
/// </summary>
/// <param name="table"></param>
/// <param name="builder"></param>
public class AccountGetService(AccountTable table, IShadowBuilder builder)
{
    private readonly SqlSource _source = new(builder.DataSource);
    private readonly IParamQuery<Account, Account> _accountQuery
        = builder.BuildResult<Account, Account>(
            table.ToQuery()
                .And(account => account.Id.Equal())
                .ToSelect()
                .SelectSelfColumns()
        );
    /// <summary>
    /// 获取账户
    /// </summary>
    /// <param name="param"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public Task<Account?> GetAsync(Account param, CancellationToken token = default)
        => _accountQuery.GetFirstAsync(_source, param, token);
}

2. 调用方式如下

var account = await accountGetService.GetAsync(new Account { Id = 1L });

3. 这个例子我们可以简化一下

  • 参数直接使用Id(类型由Account简化为long)
  • 很大一部分数据库操作都只有一个参数(GetById、GetByName等)
  • 为此定义一个含单个属性类有点浪费
  • 按上面的例子使用实体类作为参数也感觉怪怪的
  • DBShadow.net支持这种简化操作
  • 单个参数无需封装,直接使用参数类型即可
public class AccountGetService(AccountTable table, IShadowBuilder builder)
{
    private readonly SqlSource _source = new(builder.DataSource);
    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, CancellationToken token = default)
        => _accountQuery.GetFirstAsync(_source, param, token);
}
var account = await accountGetService.GetAsync(1L);

4. 特别注意不要在Dapper中这样使用

  • Dapper是不支持这种简化操作
  • 以下Dapper错误示例会抛出异常
  • System.InvalidOperationException:“Must add values for the following parameters: @Id”
await using var conn = _dataSource.CreateConnection();
var sql = "SELECT \"Id\",\"Title\",\"Content\",\"Done\",\"LastTime\" FROM \"Todo\" WHERE \"Id\"=@Id";
var first = await conn.QueryFirstOrDefaultAsync<Todo>(sql, 1L);

二、 集合参数也支持简化操作

1. IN查询简化的例子

  • In的参数名是可选和前面例子的Equal是一样的,默认字段名
  • In查询的实参支持数组、集合及字典
  • 只有In一个集合参数时支持直接传数组、集合或字典
  • 这时参数名(eg: AccountIds)就无所谓取什么名了
  • 因为不需要按参数名反射获取属性了
/// <summary>
/// 批量账户获取服务
/// </summary>
/// <param name="table"></param>
/// <param name="builder"></param>
public class AccountBatchService(AccountTable table, IShadowBuilder builder)
{
    private readonly SqlSource _source = new(builder.DataSource);
    private readonly IParamQuery<long[], Account> _accountQuery
        = builder.BuildResult<long[], Account>(
            table.ToQuery()
                .And(account => account.Id.In("AccountIds"))
                .ToSelect()
                .SelectSelfColumns()
        );
    /// <summary>
    /// 批量获取账户
    /// </summary>
    /// <param name="accountIds"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public IAsyncEnumerable<Account> GetAsync(long[] accountIds, CancellationToken token = default)
        => _accountQuery.QueryAsync(_source, accountIds, token);
}

2. 以上调用的例子如下

[Fact]
public async Task Batch()
{
    var count = 0;
    var service = new AccountBatchService(table, builder);
    var list = service.GetAsync([1L, 2L, 3L]);
    await foreach (var item in list)
    {
        _output.WriteLine($"{item.Id}:{item.Amount}");
        count++;
    }
    Assert.Equal(3, count);
}
// 1:100
// 2:200
// 3:300

三、泛型查询

1. 泛型服务类代码

  • 该服务用来按字段Id查询表Account
  • 参数TParam是泛型,这样可以直接使用DTO参数来查询,减少类型转化的开销
  • 返回值TAccount也是泛型,这样就可以直接返回视图模型或者领域模型
  • 这样的泛型服务类就非常的通用
/// <summary>
/// 账户获取泛型服务
/// </summary>
/// <param name="table"></param>
/// <param name="builder"></param>
public class AccountGetService<TParam, TAccount>(AccountTable table, IShadowBuilder builder)
{
    private readonly SqlSource _source = new(builder.DataSource);
    private readonly IParamQuery<TParam, TAccount> _accountQuery
        = builder.BuildResult<TParam, TAccount>(
            table.ToQuery()
                .And(account => account.Id.Equal())
                .ToSelect()
                .SelectSelfColumns()
        );
    /// <summary>
    /// 获取账户
    /// </summary>
    /// <param name="param"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    public Task<TAccount?> GetAsync(TParam param, CancellationToken token = default)
        => _accountQuery.GetFirstAsync(_source, param, token);
}

2. 中规中矩的调用方式

var service = new AccountGetService<Account, Account>(_table, _builder);
var account = await service.GetAsync(new Account { Id = 1L });
Assert.NotNull(account);

3. 支持简单调用方式

var service = new AccountGetService<long, Account>(_table, _builder);
var account = await service.GetAsync(1L);
Assert.NotNull(account);

四、总结

  • DBShadow.net预编译比较智能
  • 只有1个参数时支持化繁为简,支持直接传值做为参数值
  • 这样可以节约定义只有一个属性的参数类
  • 参数和返回值类型还可以定义为泛型,可以做到更加灵活

另外源码托管地址: https://github.com/donetsoftwork/DBShadow.net ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/DBShadow.net

如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!