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

推荐订阅源

博客园 - 三生石上(FineUI控件)
L
LangChain Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
U
Unit 42
B
Blog
N
Netflix TechBlog - Medium
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
M
MIT News - Artificial intelligence
D
DataBreaches.Net
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
LINUX DO - 热门话题
C
CERT Recently Published Vulnerability Notes
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
N
News and Events Feed by Topic
Vercel News
Vercel News
T
Tenable Blog
Security Latest
Security Latest
C
Check Point Blog
云风的 BLOG
云风的 BLOG
PCI Perspectives
PCI Perspectives
月光博客
月光博客
TaoSecurity Blog
TaoSecurity Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Project Zero
Project Zero
雷峰网
雷峰网
IT之家
IT之家
H
Hacker News: Front Page
Microsoft Security Blog
Microsoft Security Blog
B
Blog RSS Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Last Week in AI
Last Week in AI
G
Google Developers Blog
Forbes - Security
Forbes - Security
The Register - Security
The Register - Security
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Martin Fowler
Martin Fowler
K
Kaspersky official blog
P
Proofpoint News Feed
T
Threatpost
Google Online Security Blog
Google Online Security Blog
Microsoft Azure Blog
Microsoft Azure Blog

博客园 - 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-23 · via 博客园 - xiangji

一、mysql参数的成本

  • 使用BenchmarkDotNet测试

1. 测试代码如下

  • CreateParameter直接构造参数
  • Clone预先构造参数名和类型,复制后只设置参数值
private static readonly MySqlCommand _command = new();
private static MySqlParameter _idParameter;

[Benchmark(Baseline = true)]
public DbParameter CreateParameter()
{
    var id = _command.CreateParameter();
    id.ParameterName = "Id";
    id.DbType = System.Data.DbType.Int64;
    id.Value = 1L;
    return id;
}
[Benchmark]
public DbParameter Clone()
{
    var id = _idParameter.Clone();
    id.Value = 1L;
    return id;
}

[GlobalSetup]
public void Setup()
{
    _idParameter = _command.CreateParameter();
    _idParameter.ParameterName = "Id";
    _idParameter.DbType = System.Data.DbType.Int64;
}

2. 测试结果如下

  • 通过复制方式节省了80%的时间
  • 感觉有搞头,所以希望把复制参数的功能加入到DBShadow.net中用来提高性能
Method Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
CreateParameter 58.60 ns 0.185 ns 0.213 ns 1.00 0.0064 112 B 1.00
Clone 11.01 ns 0.387 ns 0.431 ns 0.19 0.0064 112 B 1.00

二、Clone重写参数预编译

1. 生成的代码如下

  • 预编译反射command和参数类型,以便生成更原生更快的代码
  • 其中cached是预先构造好的参数缓存作为常量
  • cached含有参数名和类型信息
  • Clone后只需要设置参数值即可
(DbCommand command, Todo param) =>
{
    MySqlParameterCollection parameters = ((MySqlCommand)command).Parameters;
    MySqlParameter t = cached.Clone();
    t.Value = param.Id;
    // MySqlParameter Add(MySqlParameter parameter)
    return parameters.Add(t);
}

2. 选择更合适的原生方法

  • MySqlParameterCollection有两个Add方法
  • 很明显重载Add(MySqlParameter parameter)更合适
  • 调用重载Add(object value)需要做多次类型转换
  • 既然生成的代码可以控制,就需要选用更合适的重载
public MySqlParameter Add(MySqlParameter parameter);
public override int Add(object value);

3. 类型嗅探

  • 预编译是ShadowBuilder负责,基于ADO.net(DbCommand)
  • 为了生成更原生的代码,需要知道具体的Command类型
  • 所以ShadowBuilder需要更多实际的类型信息
  • 为此本来ShadowExecutor才需要的数据源信息,现在ShadowBuilder也需要

3.1 ShadowBuilder以前的代码

class ShadowBuilder(ISqlEngine engine, IMapperOptions options);

3.2 ShadowBuilder现在的代码

  • 其中CommandBuilder可以通过DbDataSourcet推导出来
  • 也就是实际只增加了DbDataSource
class ShadowBuilder(IMapperOptions options, ISqlEngine engine, DbDataSource dataSource, CommandBuilder commandBuilder);
var command = dataSource.CreateConnection().CreateCommand();
var parameterType = command.CreateParameter().GetType();
CommandBuilder commandBuilder = CommandBuilder.Create(command, parameterType);

3.3 嗅探的过程

  • 通过DbDataSource推导出CommandBuilder
  • 其中CreateConnection、CreateCommand和CreateParameter等方法并不实际执行数据库IO操作,只是用来嗅探类型信息
var command = dataSource.CreateConnection().CreateCommand();
var commandType = command.GetType();
var parametersProperty = commandType.GetProperty("Parameters", BindingFlags.Instance | BindingFlags.DeclaredOnly);
var parametersType = parametersProperty.PropertyType;
var parameterType = command.CreateParameter().GetType();
var addParameterMethod = parametersType.GetMethod("Add", [parameterType]);

4. ShadowExecutor变化比较小

  • 只是把类ShadowBuilder改为IShadowBuilder接口
  • 实际还是ShadowBuilder变化

4.1 ShadowExecutor以前的代码

class ShadowExecutor(ShadowBuilder builder, SqlSource source);

4.2 ShadowExecutor现在的代码

class ShadowExecutor(IShadowBuilder builder, SqlSource source);

5. ShadowBuilder和ShadowCachedBuilder的关系

5.1 ShadowCachedBuilder以前是ShadowBuilder的子类

class ShadowCachedBuilder : ShadowBuilder;

5.2 ShadowCachedBuilder现在是ShadowBuilder的包装类

  • 通过接口IShadowBuilder来实现
  • 通过original成员调用原有的ShadowBuilder功能
  • 这样避免ShadowBuilder的复杂度增加影响到ShadowCachedBuilder
  • ShadowCachedBuilder只负责缓存编译好的对象
class ShadowCachedBuilder(ShadowBuilder original)
    : IShadowBuilder;

三、复杂的现实世界

1. SqliteParameter不支持复制

  • SqliteParameter没有实现ICloneable接口
  • SqliteParameter也没有Clone方法

2. SqlParameter可以复制,但性能不佳

  • SqlParameter(Mssql)实现了ICloneable接口

2.1 测试代码如下

private readonly SqlCommand _command = new(); 
private ICloneable _idParameter;

[Benchmark(Baseline = true)]
public DbParameter CreateParameter()
{
    var id = _command.CreateParameter();
    id.ParameterName = "Id";
    id.DbType = System.Data.DbType.Int64;
    id.Value = 1L;
    return id;
}
[Benchmark]
public DbParameter Clone()
{
    var id = (SqlParameter)_idParameter.Clone();
    id.Value = 1L;
    return id;
}
[GlobalSetup]
public void Setup()
{
    var idParameter = _command.CreateParameter();
    idParameter.ParameterName = "Id";
    idParameter.DbType = System.Data.DbType.Int64;
    _idParameter = idParameter;
}

2.2 测试结果如下

  • Clone方式比直接CreateParameter方式还慢
  • 这就是为什么CommandBuilder可以推导出来还要作为ShadowBuilder参数的原因
  • 现在只能把选择权交给用户,让用户决定是否启用Clone方式
Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc Ratio
CreateParameter 15.38 ns 0.942 ns 0.967 ns 14.53 ns 1.00 0.09 0.0106 184 B 1.00
Clone 24.84 ns 0.169 ns 0.194 ns 24.81 ns 1.62 0.10 0.0106 184 B 1.00

四、ParameterBuilder

  • ParameterBuilder负责反射参数实际类型和方法,用于生成更高效的代码
  • ParameterBuilder是抽象类,有3个具体实现

1. BuildByNamedConstructor

  • 通过含参数名构造函数创建参数

2. BuildByDefaultConstructor

  • 通过默认构造函数创建参数,然后设置参数名

3. BuildByMethod

  • 通过command的CreateParameter方法创建参数,然后设置参数名

4. ParameterBuilder的成员

  • New方法用于创建参数实例
  • SetParameterName方法用于设置参数的ParameterName属性
  • SetDbType方法用于设置参数的DbType属性
  • SetValue方法用于设置参数的Value属性
  • GetParameterName方法用于生成集合参数的子参数名

5. 集合参数

  • 大部分数据库并不支持集合参数
  • 因此需要生成多个单独的子参数来实际执行
  • eg: IN @Ids 需要转化为 IN (@Ids0, @Ids1, @Ids2),Ids为集合参数
  • 集合参数很特殊,需要实际执行的时候才确定实际子参数的个数
  • 通过预编译可以生成遍历实参集合生成子参数的代码

五、IParameterFactory

  • IParameterFactory就是参数处理的抽象
  • ParameterFactory是默认实现
  • CloneParameterFactory是Clone方式实现
  • 而IParameterFactory作为CommandBuilder的成员,处理参数部分的逻辑

1. ParameterFactory

  • 默认的参数处理实现
  • ParameterFactory包含ParameterBuilder,用于生成更高效的参数处理代码
  • CloneParameterFactory也需要调用ParameterFactory的功能

1.1 数据库类型处理

  • 可以通过重写CheckDbTypeCore方法来处理特殊的数据库类型映射需求
/// <summary>
/// 处理数据库类型(预留扩展处理特殊需求)
/// </summary>
/// <param name="valueType"></param>
protected virtual DbType CheckDbTypeCore(Type valueType)
    => CheckDbType(valueType);
/// <summary>
/// 默认数据库类型
/// </summary>
/// <param name="valueType"></param>
/// <returns></returns>
public static DbType CheckDbType(Type valueType)
{
    if (valueType.IsArray)
        return DbType.Binary;
    var typeCode = Type.GetTypeCode(valueType);
    return typeCode switch
    {
        TypeCode.Byte => DbType.Byte,
        TypeCode.SByte => DbType.SByte,
        TypeCode.Int16 => DbType.Int16,
        TypeCode.UInt16 => DbType.UInt16,
        TypeCode.Int32 => DbType.Int32,
        TypeCode.UInt32 => DbType.UInt32,
        TypeCode.Int64 => DbType.Int64,
        TypeCode.UInt64 => DbType.UInt64,
        TypeCode.Single => DbType.Single,
        TypeCode.Double => DbType.Double,
        TypeCode.Decimal => DbType.Decimal,
        TypeCode.Boolean => DbType.Boolean,
        TypeCode.String => DbType.String,
        TypeCode.Char => DbType.StringFixedLength,
        TypeCode.DateTime => DbType.DateTime,
        _ => DbType.Object,
    };
}

1.2 CreateParameter方法

  • 实际CreateParameter专用于CloneParameterFactory
  • 用于创建参数原型,以便后续Clone使用
/// <summary>
/// 构造参数
/// </summary>
/// <param name="valueType"></param>
/// <returns></returns>
DbParameter CreateParameter(Type valueType);
/// <summary>
/// 构造参数
/// </summary>
/// <param name="name"></param>
/// <param name="valueType"></param>
/// <returns></returns>
DbParameter CreateParameter(string name, Type valueType);

1.3 实现接口IParameterBuilder

  • 以下Create方法实际用于构造参数表达式
  • 其中_parameterBuilder就是ParameterBuilder实例,前面有介绍
public Expression Create(IEmitBuilder builder, string name, Expression value)
    => Create(builder, Expression.Constant(name), value);
/// <summary>
/// 构造参数
/// </summary>
/// <param name="builder"></param>
/// <param name="parameterName"></param>
/// <param name="value"></param>
/// <returns></returns>
public Expression Create(IEmitBuilder builder, Expression parameterName, Expression value)
{
    // var parameter = command.CreateParameter();
    // parameter.ParameterName = parameterName;
    var parameter = _parameterBuilder.New(builder, parameterName);
    // parameter.DbType = dbType;
    _parameterBuilder.SetDbType(builder, parameter, CheckDbTypeCore(value.Type));
    // parameter.Value = value;
    _parameterBuilder.SetValue(builder, parameter, value);
    return parameter;
}

1.4 实现接口ICollectParameterBuilder

  • ICollectParameterBuilder用于处理集合参数的子参数
public Expression CreateIndex(IEmitBuilder builder, Expression prefix, Expression index, Expression value)
    => Create(builder, ParameterBuilder.GetParameterName(prefix, index), value);

2. CloneParameterFactory

  • CloneParameterFactory由CloneParameterBuilder和CloneCollectParameterBuilder组成
  • CloneParameterBuilder通过Clone方式创建参数
  • CloneCollectParameterBuilder通过Clone方式创建集合参数

3. CloneParameterBuilder

3.1 CloneParameterBuilder包含ParameterBuilder、ParameterFactory和IEmitConverter成员

  • ParameterBuilder用于处理参数值
  • ParameterFactory用于生成参数原型,以便Clone使用
  • IEmitConverter用于复制参数
class CloneParameterBuilder(ParameterBuilder original, ParameterFactory factory, IEmitConverter cloneConverter);

3.2 GetProtoType方法

  • 用于生成参数原型并缓存
  • 调用ParameterFactory的CreateParameter方法生成参数原型
  • 按参数名和类型缓存
    /// <summary>
    /// 获取原型缓存
    /// </summary>
    /// <param name="name"></param>
    /// <param name="valueType"></param>
    /// <returns></returns>
    public DbParameter GetProtoType(string name, Type valueType)
    {
        var key = new NameTypedCacheKey(name, valueType);
        if (_protoTypes.TryGetValue(key, out var cached))
            return cached;
#if NET9_0_OR_GREATER
        lock (_lock)
#else
        lock (_protoTypes)
#endif
        {
            if (_protoTypes.TryGetValue(key, out cached))
                return cached;
            return _protoTypes[key] = _factory.CreateParameter(name, valueType);
        }
    }

3.3 Create方法

  • Create用于构造参数表达式
  • 先调用GetProtoType方法获取参数原型
  • 该过程发生在预编译阶段,不会影响运行时性能
  • 通过缓存避免不同方法相同参数原型的重复创建
  • 通过cloneConverter生成Clone调用表达式
  • 最后调用ParameterBuilder设置参数值

4. CloneCollectParameterBuilder

  • CloneCollectParameterBuilder用于处理集合参数

3.1 CloneCollectParameterBuilder也是包含ParameterBuilder、ParameterFactory和IEmitConverter成员

  • ParameterBuilder用于处理参数值
  • ParameterFactory用于生成参数原型,以便Clone使用
  • IEmitConverter用于复制参数
class CloneCollectParameterBuilder(ParameterBuilder original, ParameterFactory factory, IEmitConverter cloneConverter);

3.2 GetProtoType方法

  • 用于生成集合参数原型并缓存
  • 调用ParameterFactory的CreateParameter方法生成参数原型
  • 按类型缓存
  • CollectParameterPrototype(集合参数原型)用于实际处理集合参数的子参数
    /// <summary>
    /// 获取原型缓存
    /// </summary>
    /// <param name="valueType"></param>
    /// <returns></returns>
    public CollectParameterPrototype GetProtoType(Type valueType)
    {
        if (_protoTypes.TryGetValue(valueType, out var protoType))
            return protoType;
#if NET9_0_OR_GREATER
        lock (_lock)
#else
        lock (_protoTypes)
#endif
        {
            if (_protoTypes.TryGetValue(valueType, out protoType))
                return protoType;
            var parameter = _factory.CreateParameter(valueType);
            return _protoTypes[valueType] = new(_original, parameter, _cloneConverter);
        }
    }

4. CollectParameterPrototype

  • 集合参数原型

4.1 包含ParameterBuilder、IEmitConverter和原型缓存

class CollectParameterPrototype(ParameterBuilder original, ConstantExpression cached, IEmitConverter cloneConverter);

4.2 CreateIndex方法

  • CreateIndex用于构造集合参数的子参数
  • 通过cloneConverter生成Clone调用表达式
  • 最后调用ParameterBuilder设置参数名和参数值

六、ToExecutor

  • 由于ShadowBuilder包含数据源可以很方便转化为ShadowExecutor
  • 增加ToExecutor方法简化操作
  • ToExecutor是IShadowBuilder的方法,所以ShadowCachedBuilder也支持
/// <summary>
/// 转化为执行器
/// </summary>
/// <param name="commandTimeout"></param>
/// <returns></returns>
ShadowExecutor ToExecutor(int? commandTimeout = null);

七、总结

  • 通过嗅探实际类型生成更高效的参数处理代码
  • 不同数据库差异为此带来了复杂性
  • 部分数据库可以通过Clone方式创建参数提高性能

1. 一般使用示例

  • 一般使用CreateCache方法创建ShadowBuilder
  • 如果是一次性使用无需缓存可以使用Create方法代替
var engine = new MySqlEngine();
var dataSource = new MySqlDataSource(ConnectionString);
var builder = ShadowBuilder.CreateCache(Mapper.Default, engine, dataSource);
var select = table.ToQuery()
    .And(table.Id.Equal())
    .ToSelect()
    .SelectSelfColumns();
var compiled = select.BuildQuery<Todo, Todo>(builder);

1.1 参数处理生成如下代码

(DbCommand command, Todo param) =>
{
    var parameters = ((MySqlCommand)command).Parameters;
    var t = new MySqlParameter();
    t.ParameterName = "Id";
    t.DbType = DbType.Int64;
    t.Value = param.Id;

    return parameters.Add(t);
}

2. 启用Clone方式示例

  • 启用Clone要复杂一点
  • 需要通过CloneParameter生成一个ParameterFactory
  • 然后传入ShadowBuilder的CreateCache方法中
var engine = new MySqlEngine();
var dataSource = new MySqlDataSource(ConnectionString);
var parameterFactory = ParameterFactory.CloneParameter(new MySqlCommand())
var builder = ShadowBuilder.CreateCache(Mapper.Default, engine, dataSource, parameterFactory);
var select = table.ToQuery()
    .And(table.Id.Equal())
    .ToSelect()
    .SelectSelfColumns();
var compiled = select.BuildQuery<Todo, Todo>(builder);

2.1 参数处理生成如下代码

  • cached是预先构造好作为常量的参数缓存
  • 很明显如果Clone比new更快的话,这个代码会更高效
  • Mysql下此方法耗时为原来的20%(也就是性能提高4倍)
(DbCommand command, Todo param) =>
{
    var parameters = ((MySqlCommand)command).Parameters;
    var t = cached.Clone();
    t.Value = param.Id;

    return parameters.Add(t);
}

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

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