# DBShadow **Repository Path**: donetsoftwork/DBShadow.net ## Basic Information - **Project Name**: DBShadow - **Description**: 一个.net高性能ORM工具 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-01-11 - **Last Updated**: 2026-01-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DBShadow出世,Dapper.net的天花板盖不住了 ## 一、DBShadow是什么 >* DBShadow是.net开源的高性能ORM >* DBShadow使用开源项目ShadowSql高效拼接sql >* DBShadow使用开源项目PocoEmit.Mapper高效映射查询参数和查询结果 >* 也就是说SqlBuilder(ShadowSql)+OOM(PocoEmit.Mapper)=ORM(DBShadow) ## 二、DBShadow和Dapper对比一下 ### 1. Dapper代码 ~~~csharp await using var conn = _dataSource.CreateConnection(); var sql = "SELECT \"Id\",\"Title\",\"Content\",\"Done\",\"LastTime\" FROM \"Todo\" WHERE \"Id\"=@Id"; var first = await conn.QueryFirstOrDefaultAsync(sql, _todo); ~~~ ~~~csharp DbDataSource _dataSource = new StringDataSource("Data Source=file::memory:;Cache=Shared", conn => new SqliteConnection(conn)); ~~~ ### 2. DBShadow代码 >* 使用SqliteEngine处理数据库方言 >* 使用Mapper.Default处理类型映射 >* ShadowCachedBuilder用来编译和缓存 ~~~csharp var first = await _shadowSelect.GetFirstAsync(_executor, _todo); ~~~ ~~~csharp ISqlEngine engine = new SqliteEngine(); ShadowExecutor _executor = ShadowBuilder.CreateCache(engine, Mapper.Default); TodoTable _table = new("Todo"); ISelect _shadowSelect_shadowSelect = _table.ToQuery() .And(_table.Id.Equal()) .ToSelect() .SelectSelfColumns(); ~~~ ### 3. 用BenchmarkDotNet对比一下 >* DBShadow比Dapper快10% >* 内存也占优 | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | |--------------- |----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| | Dapper | 4.636 us | 0.0194 us | 0.0216 us | 1.00 | 0.01 | 0.1400 | - | 2.38 KB | 1.00 | | DBShadow | 4.030 us | 0.0152 us | 0.0175 us | 0.87 | 0.01 | 0.1300 | - | 2.2 KB | 0.92 | ## 三、再用Mysql对比一下 ### 1. Dapper代码 ~~~csharp await using var conn = _dataSource.CreateConnection(); var sql = "SELECT \"Id\",\"Title\",\"Content\",\"Done\",\"LastTime\" FROM \"Todo\" WHERE \"Id\"=@Id"; var first = await conn.QueryFirstOrDefaultAsync(sql, _todo); ~~~ ~~~csharp string ConnectionString = "Server=localhost;Database=Benchmarks;User=root;Password=123456;"; DbDataSource _dataSource = MySqlDataSource(ConnectionString); ~~~ ### 2. DBShadow代码 >* 使用MySqlEngine处理数据库方言 >* 使用Mapper.Default处理类型映射 >* ShadowCachedBuilder用来编译和缓存 ~~~csharp var first = await _shadowSelect.GetFirstAsync(_executor, _todo); ~~~ ~~~csharp ISqlEngine engine = new MySqlEngine(); ShadowExecutor _executor = ShadowBuilder.CreateCache(engine, Mapper.Default); TodoTable _table = new("Todo"); ISelect _shadowSelect_shadowSelect = _table.ToQuery() .And(_table.Id.Equal()) .ToSelect() .SelectSelfColumns(); ~~~ ### 3. 再用BenchmarkDotNet对比一下 >* DBShadow比Dapper只快3% >* 内存占优 >* 由于MySql耗时几乎是Sqlite的100倍,执行代码是一样的(能快3%就很不容易了) | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | |--------------- |---------:|---------:|---------:|------:|--------:|----------:|------------:| | Dapper | 397.7 us | 8.83 us | 9.81 us | 1.00 | 0.03 | 8.08 KB | 1.00 | | DBShadow | 383.9 us | 14.62 us | 15.64 us | 0.97 | 0.04 | 7.23 KB | 0.89 | ## 四、DBShadow支持事务操作 ### 1. 举个事务回滚的栗子 >* 建表Accounts >* 账号1初始化余额为100 >* 查询账号1余额为100 >* 开启事务 >* 使用事务把余额设置为90 >* 在事务下查询余额为90 >* 事务回滚 >* 再次查询账号1余额为100 >* DBShadow事务操作很优雅 >* 是否事务只与使用哪个处理器或数据源有关 >* 正常处理器或数据源可以很方便的转化为事务相关对象 ~~~csharp var table = new AccountTable(); try { await SqliteExecutor.ExecuteAsync(table.ToCreate()); // 建表Accounts } catch { } await new SingleInsert(table) .Insert(table.Id.InsertValue(1L)) .Insert(table.Amount.InsertValue(100L)) .ExecuteAsync(SqliteExecutor); // 账号1初始化余额为100 // 查询账号1 var query = table.ToSqlQuery().Where("Id=1"); var amount = await query .ToSelect() .Select(account => account.Amount) .GetScalarAsync(SqliteExecutor); // 查询账号1余额为100 Assert.Equal(100L, amount); // 开启事务 await using var transaction = await SqliteExecutor.BeginTransaction(); { await query.ToUpdate() .Set(account => account.Amount.AssignValue(90L)) .ExecuteAsync(transaction); // 使用事务把余额设置为90 var amount2 = await query .ToSelect() .Select(account => account.Amount) .GetScalarAsync(transaction); // 在事务下查询余额为90 // 减成了90 Assert.Equal(90L, amount2); // 事务回滚 await transaction.RollbackAsync(); } var amount3 = await query .ToSelect() .Select(account => account.Amount) .GetScalarAsync(SqliteExecutor); // 回滚后恢复为100 Assert.Equal(100L, amount3); ~~~ ### 2. 再举个事务提交和预编译的栗子 >* 事务提交和事务回滚特别相近,为此增加DBShadow预编译的内容 >* 建表预编译 >* 插入操作预编译 >* 查询账号余额预编译 >* 修改账号余额预编译 >* 建表Accounts >* 账号1初始化余额为100 >* 查询账号1余额为100 >* 开启事务 >* 使用事务把余额设置为90 >* 在事务下查询余额为90 >* 事务提交 >* 再次查询账号1余额为90 >* 预编译能提高执行性能也稳定性 >* 再事务操作之前预编译很有必要 >* 预编译之后的结果对是否事务数据源都是一样的使用方式(也就是业务代码可以做到通用) ~~~csharp var builder = SqliteExecutor.Builder; var table = new AccountTable(); var query = table.ToSqlQuery().Where("Id=1"); #region Compile // 建表预编译 var createCompiled = builder.BuildQuery(table.ToCreate()); // 插入操作预编译 var insertCompiled = builder.BuildQuery(new SingleInsert(table) .Insert(table.Id.InsertValue(1L)) .Insert(table.Amount.InsertValue(100L))); // 查询账号余额预编译 var amountCompiled = builder.BuildScalar(query .ToSelect() .Select(account => account.Amount)); // 修改账号余额预编译 var updateCompiled = builder.BuildQuery(query.ToUpdate() .Set(account => account.Amount.AssignValue(90L))); #endregion try { await createCompiled.ExecuteAsync(SqliteSource); // 建表Accounts } catch { } await insertCompiled.ExecuteAsync(SqliteSource); // 账号1初始化余额为100 var amount = await amountCompiled.GetScalarAsync(SqliteSource); // 查询账号1余额为100 Assert.Equal(100L, amount); // 开启事务 await using var transaction = await SqliteSource.BeginTransaction(); { await updateCompiled.ExecuteAsync(transaction); // 使用事务把余额设置为90 var amount2 = await amountCompiled.GetScalarAsync(transaction); // 在事务下查询余额为90 Assert.Equal(90L, amount2); await transaction.CommitAsync(); // 事务提交 } var amount3 = await amountCompiled.GetScalarAsync(SqliteSource); // 再次查询账号1余额为90 Assert.Equal(90L, amount3); ~~~ ## 五、DBShadow解密 ### 1. 首先DBShadow基于现代ADO.net #### 1.1 DbDataSource >* 数据连接基于System.Data.Common.DbDataSource >* DbDataSource的重要方法CreateConnection >* 相当于数据库连接工厂或连接池 #### 1.2 StringDataSource >* 虽然微软推出DbDataSource很多年了,但是业界支持的并不是很好 >* 比如Sqlite不支持DbDataSource >* 就算是System.Data也只能.net7+才支持 >* 这个破破烂烂的世界,需要缝缝补补 >* StringDataSource支持net4.5+和netstandard2.0+ >* 在.net7+下StringDataSource是DbDataSource的子类 >* 其他情况下DBShadow使用StringDataSource直接代替DbDataSource #### 1.3 IAsyncEnumerable\<\> >* 这是异步下的迭代器 >* 在异步操作IO流下实现延迟加载和流式计算 >* DBShadow的列表都是基于IAsyncEnumerable\<\> >* EFCore也支持IAsyncEnumerable\<\>,但Dapper不支持 #### 2. ShadowSql >* 使用ShadowSql拼接sql >* ShadowSql也是笔者的开源项目,之前就有很多相关的介绍 >* [ShadowSql.net之正确使用方式](https://www.cnblogs.com/xiangji/p/18909458) #### 3. PocoEmit.Mapper >* 使用PocoEmit.Mapper来映射 >* 把参数映射为SqlParameter >* 把DataReader映射为实体 >* 使得DBShadow继承了PocoEmit.Mapper的可配置性、可扩展性和依赖注入等功能,这些EFCore和Dapper都是做不到的 >* PocoEmit.Mapper也是笔者的开源项目,之前就有很多相关的介绍 >* [婶可忍叔不可忍的AutoMapper,你还用吗?](https://www.cnblogs.com/xiangji/p/19059979) >* [如何使用PocoEmit.Mapper替代AutoMapper](https://www.cnblogs.com/xiangji/p/19062936) >* [PocoEmit遥遥领先于AutoMapper之打通充血模型的任督二脉](https://www.cnblogs.com/xiangji/p/19125327) 另外源码托管地址: https://github.com/donetsoftwork/DBShadow.net ,欢迎大家直接查看源码。 gitee同步更新:https://gitee.com/donetsoftwork/DBShadow.net 如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!