16-C#.Net-自研ORM框架-学习笔记
本文记录从零手写一个 ORM 框架(Zhaoxi.DbProxy)的完整过程,系统梳理其中涉及的 C# 高级特性与设计思想。
零、框架全貌
在深入各个知识点之前,先从整体上认识这个框架长什么样、能做什么、怎么用。
0.1 项目结构
Zhaoxi.DbProxy.Project/
├── Zhaoxi.DbProxy/ # 框架核心库
│ ├── DbProxyCore.cs # 抽象基类,用户继承它来配置数据库连接
│ ├── DbProxyExtension.cs # 静态扩展类,所有 CRUD 方法都在这里
│ ├── DbProxyCoreNewAsync.cs # 异步版本方法(partial 类)
│ ├── SqlBuilderExtension/ # SQL 自动生成(ObjectManagerProvider<T>)
│ ├── Register/
│ │ └── DbProxyOptions.cs # 连接配置(主库 + 从库集合)
│ ├── Model/
│ │ ├── BaseModel.cs # 所有实体类的基类,含主键 Id
│ │ └── PageResult.cs # 分页结果封装
│ ├── MapExtension/
│ │ └── ZxPrimaryKeyAttribute.cs # 标记主键字段的特性
│ ├── PolicyExtension/
│ │ ├── IClusterPolicy.cs # 从库选择策略接口
│ │ └── DefaultClusterPolicy.cs # 内置随机策略
│ ├── ValidateExtension/ # 数据验证特性体系
│ └── CodeMap/ # DbFirst / CodeFirst 代码生成
│
├── Zhaoxi.DbProxyExtension/ # 扩展库(表名/字段名映射、表达式树解析)
│ ├── MapExtension/
│ │ ├── ZxTableAttribute.cs # 类名 → 表名映射
│ │ ├── ZxColumnAttribute.cs # 属性名 → 字段名映射
│ │ └── ZxMapManager.cs # GetName() 扩展方法
│ └── ExpressionExtension/
│ └── Visitor/
│ └── ConditionBuilderVisitor.cs # 表达式树 → SQL WHERE 子句
│
└── Zhaoxi.DbProxy.Models/ # 示例实体类库
└── Models/
├── Commodity_ZhaoXi.cs
└── Company_ZhaoXi.cs
0.2 完整 API 一览
框架提供同步和异步两套 API,全部以扩展方法形式挂载在 DbProxyCore 上:
| 方法 | 说明 |
|---|---|
Find<T>(int id) |
按主键查询单条记录 |
Query<T>(Expression<Func<T, bool>> expr) |
按条件查询集合 |
PagerQuery<T>(expr, pageIndex, pageSize, out total, orderType, orderFields) |
分页查询,同时返回总记录数 |
Insert<T>(T t) |
新增单条记录,返回受影响行数 |
InsertList<T>(IList<T> list) |
批量新增,内部自动开启事务 |
Update<T>(T t) |
按主键更新单条记录 |
UpdateList<T>(IList<T> list) |
批量更新,内部自动开启事务 |
Delete<T>(int id) |
按主键删除 |
DeleteWhere<T>(Expression<Func<T, bool>> expr) |
按条件删除 |
DbFirstCreateClass(path, namespace) |
DbFirst:从数据库生成实体类文件 |
DbCodeFirstInit(dbName, dllPath) |
CodeFirst:从实体类创建数据库和表 |
异步版本在方法名后加 Async,返回 Task<T>,用法完全对称。
0.3 完整使用示例
第一步:定义实体类
// 继承 BaseModel(含主键 Id)
// 类名与表名一致时不需要特性;不一致时用 ZxTable 声明
[ZxTable("Commodity")]
public class Commodity_ZhaoXi : BaseModel
{
public int ProductId { get; set; }
public int CategoryId { get; set; }
[ZxColumn("Title")] // 属性名与字段名不一致时映射
[ZxRequired("标题不能为空")] // 数据验证
[ZxLength(1, 50, "标题长度1到50之间")]
public string TitleInfo { get; set; }
public decimal Price { get; set; }
public string Url { get; set; }
public string ImageUrl { get; set; }
}
第二步:创建数据库代理
// 方式一:单库(只有主库)
public class MyDbProxy : DbProxyCore
{
public override void OnConfiguring(DbProxyOptions options)
{
options.UseSqlServer("Server=.;Database=MyDb;User ID=sa;Password=123456;");
}
}
// 方式二:读写分离(主库 + 多从库)
DbProxyOptions options = new DbProxyOptions
{
MasterConnectionString = "Server=主库IP;Database=MyDb;...",
SlaveConnectionConfigList = new List<SlaveConnectionConfig>
{
new() { ConnectionString = "Server=从库1;...", HitRate = 20 },
new() { ConnectionString = "Server=从库2;...", HitRate = 20 },
}
};
// 注入自定义从库选择策略(不传则默认随机)
DbProxyCore db = new MyDbProxy(options, new CustomClusterPolicy());
第三步:执行 CRUD 操作
DbProxyCore db = new MyDbProxy();
// 查询
var item = db.Find<Commodity_ZhaoXi>(1001);
var list = db.Query<Commodity_ZhaoXi>(c => c.Price > 100 && c.CategoryId == 5);
// 分页查询
var page = db.PagerQuery<Commodity_ZhaoXi>(
c => c.Price > 100,
pageIndex: 2,
pageSize: 10,
out int total,
OrderByType.Desc,
c => c.Price // 按价格降序
);
Console.WriteLine($"共 {total} 条,当前页 {page.Count} 条");
// 新增
var newItem = new Commodity_ZhaoXi { TitleInfo = "新商品", Price = 99.9m };
db.Insert(newItem);
// 批量新增(自动事务)
db.InsertList(new List<Commodity_ZhaoXi> { item1, item2, item3 });
// 修改
item.Price = 199.9m;
db.Update(item);
// 删除
db.Delete<Commodity_ZhaoXi>(1001);
db.DeleteWhere<Commodity_ZhaoXi>(c => c.Price < 10);
第四步(可选):使用异步 API
var item = await db.FindAsync<Commodity_ZhaoXi>(1001);
var list = await db.QueryAsync<Commodity_ZhaoXi>(c => c.Price > 100);
await db.InsertAsync(newItem);
await db.InsertListAsync(batchList);
0.4 框架内部执行流程
以 db.Query<Commodity>(c => c.Price > 100) 为例,框架内部的完整执行链路:
调用方
│
▼
DbProxyExtension.Query<T>()
│ 1. 从 ObjectManagerProvider<T> 取缓存好的 SELECT SQL 模板
│ 2. 用 ConditionBuilderVisitor 解析 Lambda → WHERE 子句 + SqlParameter[]
│ 3. 拼装完整 SQL
│
▼
ExecuteSQL<T>() ← 通用执行方法(委托模式)
│ 4. 根据 ReadOrWrite.Read 选择从库连接
│ └─ IClusterPolicy.GetPolicy() ← 策略模式(随机/轮询/权重)
│ 5. 创建 SqlConnection + SqlCommand
│ 6. 设置 SQL 和参数
│ 7. conn.Open()
│ 8. 执行传入的委托 func(cmd)
│
▼
SqlDataReaderToList<T>()
│ 9. ExecuteReader() 读取结果集
│ 10. 循环 reader.Read()
│ 11. 反射创建 T 实例,逐属性赋值(DBNull → null)
│
▼
返回 List<T> 给调用方
0.5 框架用到的核心技术汇总
| 技术 | 在框架中的作用 |
|---|---|
| 反射 | 动态获取类型信息、创建对象、给属性赋值 |
| 泛型 | 一套方法处理所有实体类型,保持类型安全 |
| 泛型缓存 | 每个类型的 SQL 模板只生成一次,后续直接读静态字段 |
| 特性(Attribute) | 声明主键、表名/字段名映射、数据验证规则 |
| 委托(Func) | 把可变的执行逻辑作为参数传入,消除重复代码 |
| 表达式目录树 | 把 Lambda 条件解析成 SQL WHERE 子句 |
| 策略模式 | 从库选择策略可插拔,框架不写死具体实现 |
| 扩展方法 | 为 DbProxyCore 挂载所有 CRUD 方法,保持基类简洁 |
| partial 类 | 同步/异步方法分文件维护,代码结构清晰 |
| 事务 | 批量操作保证原子性,要么全成功要么全回滚 |
| async/await | 全套异步 API,不阻塞线程,适合高并发场景 |
一、反射(Reflection)
1.1 什么是反射
反射是 .NET 运行时提供的一种能力,允许程序在运行期间动态获取类型信息、创建对象、调用方法、读写属性。它是 ORM、依赖注入、序列化等框架的底层基础。
1.2 常用操作
Type type = typeof(Commodity);
// 创建对象(调用无参构造函数)
object obj = Activator.CreateInstance(type);
// 获取所有属性
PropertyInfo[] props = type.GetProperties();
// 给属性赋值
foreach (var prop in props)
{
prop.SetValue(obj, reader[prop.Name] is DBNull ? null : reader[prop.Name]);
}
// 获取属性值
object value = prop.GetValue(obj);
1.3 反射的性能问题
反射每次调用都需要在运行时做类型解析,性能比直接调用慢很多。在 ORM 框架中,如果每次查询都用反射动态生成 SQL,100 次查询就会触发 100 次反射。
解决方案:缓存。第一次用反射生成结果后,把结果存起来,后续直接取缓存。
二、泛型(Generics)
2.1 泛型的价值
泛型让一个方法或类可以处理不同的类型,同时保持类型安全。在 ORM 中,一个 Find<T> 方法就能查询任意实体,而不需要为每张表写一个专属方法。
// 没有泛型:每张表一个方法
public Commodity GetCommodity(int id) { ... }
public Company GetCompany(int id) { ... }
// 有了泛型:一个方法搞定所有表
public T? Find<T>(int id) where T : BaseModel { ... }
2.2 泛型约束
约束用来限制泛型参数必须满足某些条件,防止调用方传入不合适的类型:
| 约束写法 | 含义 |
|---|---|
where T : BaseModel |
T 必须继承 BaseModel(基类约束) |
where T : IInterface |
T 必须实现某接口(接口约束) |
where T : new() |
T 必须有无参构造函数 |
where T : class |
T 必须是引用类型 |
where T : struct |
T 必须是值类型 |
在 DbProxy 中,所有操作方法都加了 where T : BaseModel,这样既能用反射操作属性,又能保证传入的一定是数据库实体类。
2.3 泛型缓存 vs 普通字典缓存
这是一个非常重要的性能优化技巧。
普通字典缓存:用一个静态字典,以类型全名为 key 存储结果,每次取值需要做字典查找(哈希计算)。
public class DictionaryCache
{
private static Dictionary<string, string> _cache = new();
public static string GetCache<T>()
{
string key = typeof(T).FullName!;
if (!_cache.ContainsKey(key))
_cache[key] = BuildSql<T>();
return _cache[key];
}
}
泛型缓存:利用泛型类的特性——CLR 会为每个不同的类型参数生成一个独立的类副本,静态字段也是独立的。静态构造函数只执行一次,之后直接访问静态字段,没有任何查找开销。
public class GenericCache<T>
{
private static readonly string _sql;
static GenericCache()
{
// 只执行一次,结果存在静态字段里
_sql = BuildSql(typeof(T));
}
public static string GetCache() => _sql;
}
性能对比:经过百万次调用测试,泛型缓存比普通字典缓存快约 3~5 倍,因为它省去了字典的哈希查找过程,直接读内存中的静态字段。
ObjectManagerProvider<T> 就是泛型缓存的典型应用,它在静态构造函数中一次性生成所有 SQL 模板,后续调用直接返回缓存好的字符串。
三、特性(Attribute)
3.1 特性的本质
特性是附加在类、属性、方法等成员上的元数据标签,本质上是一个继承自 Attribute 的类。运行时可以通过反射读取这些标签,从而实现"声明式编程"。
3.2 自定义特性
// 定义:只能标记在属性上
[AttributeUsage(AttributeTargets.Property)]
public class ZxPrimaryKeyAttribute : Attribute { }
// 使用
public class BaseModel
{
[ZxPrimaryKey]
public int Id { get; set; }
}
3.3 特性在 ORM 中的应用
场景一:表名/字段名映射
当 C# 类名或属性名与数据库表名/字段名不一致时,用特性来声明映射关系:
[ZxTable("Commodity")] // 类名 Commodity_ZhaoXi → 表名 Commodity
public class Commodity_ZhaoXi : BaseModel
{
[ZxColumn("Title")] // 属性名 TitleInfo → 字段名 Title
public string TitleInfo { get; set; }
}
读取时通过扩展方法统一处理:
public static string GetName(this MemberInfo member)
{
if (member.IsDefined(typeof(ZxAbstractMapAttribute), true))
{
var attr = member.GetCustomAttribute<ZxAbstractMapAttribute>()!;
return attr.GetName();
}
return member.Name; // 没有特性就用原名
}
场景二:数据验证
在属性上标记验证规则,插入/更新前自动校验:
public class Commodity_ZhaoXi : BaseModel
{
[ZxRequired("标题不能为空")]
[ZxLength(1, 20, "标题长度必须在1到20之间")]
public string TitleInfo { get; set; }
}
验证管理器通过反射遍历所有带验证特性的属性,逐一调用验证方法:
public static bool ValiDate<T>(this T obj) where T : BaseModel
{
foreach (var prop in typeof(T).GetProperties()
.Where(c => c.IsDefined(typeof(ZxAbstractValiAttribute), true)))
{
foreach (var attr in prop.GetCustomAttributes<ZxAbstractValiAttribute>())
{
attr.ValiDate(prop.GetValue(obj)); // 验证失败会抛出异常
}
}
return true;
}
开闭原则的体现:验证特性继承自抽象基类 ZxAbstractValiAttribute,新增验证规则只需新建一个特性类,不需要修改 ValidateManager 的代码。
四、委托(Delegate)
4.1 委托解决重复代码
在 ORM 的增删改查方法中,每个方法都需要:创建连接 → 创建命令 → 设置 SQL → 打开连接 → 执行 → 返回结果。这些步骤中,只有"执行"部分是变化的,其余都是重复的。
用委托把"可变的执行逻辑"作为参数传入,把"不变的连接管理逻辑"封装在一个通用方法里:
// 通用执行方法:不变的逻辑封装在这里
private static T ExecuteSQL<T>(
DbProxyCore context,
string sql,
Func<SqlCommand, T> func, // 可变的业务逻辑通过委托传入
ReadOrWrite readOrWrite,
params SqlParameter[] sqlParameters)
{
string connStr = GetConnectionString(context, readOrWrite);
using SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = sql;
cmd.Parameters.AddRange(sqlParameters);
conn.Open();
return func.Invoke(cmd); // 执行调用方传入的逻辑
}
// 调用方只需关心自己的业务逻辑
public static T? Find<T>(this DbProxyCore context, int id) where T : BaseModel
{
string sql = ObjectManagerProvider<T>.GetFindSql();
return ExecuteSQL(context, sql, cmd =>
{
// 这里只写"查单个对象"的逻辑
return SqlDataReaderToObject<T>(cmd);
}, ReadOrWrite.Read, new SqlParameter("@id", id));
}
4.2 Func 和 Action
Func<TIn, TOut>:有参数、有返回值的委托Action<T>:有参数、无返回值的委托Func<SqlCommand, T>表示:接收一个SqlCommand,返回T类型结果
五、表达式目录树(Expression Tree)
5.1 为什么需要表达式目录树
ORM 的条件查询需要把 C# 的 Lambda 表达式转换成 SQL 的 WHERE 子句。普通 Lambda(Func<T, bool>)只能执行,无法分析其内部结构。表达式目录树(Expression<Func<T, bool>>)则把 Lambda 的结构以树形数据结构保存下来,可以在运行时遍历和分析。
// 这是一个可以被分析的表达式目录树
Expression<Func<Commodity, bool>> expr = c => c.Title == "高级班" && c.Price > 100;
// 框架解析后生成:WHERE Title=@Title0 AND Price>@Price1
5.2 ExpressionVisitor 解析原理
继承 ExpressionVisitor,重写各种 Visit 方法,用栈来拼装 SQL 片段:
public class ConditionBuilderVisitor : ExpressionVisitor
{
private Stack<string> _stack = new Stack<string>();
private List<SqlParameter> _params = new List<SqlParameter>();
// 解析二元表达式:a == b、a > b、a && b
protected override Expression VisitBinary(BinaryExpression node)
{
_stack.Push(" ) ");
base.Visit(node.Right);
_stack.Push(node.NodeType.ToSqlOperator()); // == → =, AndAlso → AND
base.Visit(node.Left);
_stack.Push(" ( ");
return node;
}
// 解析属性访问:c.Title
protected override Expression VisitMember(MemberExpression node)
{
string name = node.Member.GetName();
string paraName = $"@{name}{_params.Count}";
_params.Add(new SqlParameter(paraName, _tempValue));
_stack.Push(paraName);
_stack.Push(/* operator */);
_stack.Push(name);
return node;
}
}
5.3 表达式目录树的合并
多个条件可以合并成一个表达式:
Expression<Func<Commodity, bool>> e1 = c => c.Title.Equals("高级班");
Expression<Func<Commodity, bool>> e2 = c => c.Id == 20016;
Expression<Func<Commodity, bool>> e3 = ExpressionExtend.And(e1, e2);
// 等价于:c => c.Title.Equals("高级班") && c.Id == 20016
六、ORM 核心实现
6.1 整体架构
DbProxyCore(抽象基类)
↓ 继承
CustomDbProxy(用户自定义,重写 OnConfiguring 配置连接字符串)
↓ 扩展方法
DbProxyExtension(静态扩展类,提供 Find/Query/Insert/Update/Delete 等方法)
用户使用方式:
// 1. 继承 DbProxyCore,配置连接字符串
public class CustomDbProxy : DbProxyCore
{
public override void OnConfiguring(DbProxyOptions options)
=> options.UseSqlServer("Server=...;Database=...;");
}
// 2. 直接调用扩展方法操作数据库
DbProxyCore db = new CustomDbProxy();
Commodity item = db.Find<Commodity>(1001);
List<Commodity> list = db.Query<Commodity>(c => c.Price > 100);
db.Insert(item);
db.Update(item);
db.Delete<Commodity>(1001);
6.2 SQL 自动生成(泛型缓存)
ObjectManagerProvider<T> 在静态构造函数中,通过反射+特性一次性生成所有 SQL 模板,后续直接返回缓存:
// 查询 SQL 示例(自动根据类型生成)
// SELECT Id,ProductId,CategoryId,Title,Price FROM [Commodity] WHERE ID=@Id
// 新增 SQL(自动排除主键字段)
// INSERT INTO [Commodity] ([ProductId],[CategoryId],[Title],[Price])
// VALUES (@ProductId,@CategoryId,@Title,@Price) select IDENT_CURRENT('Commodity')
// 分页 SQL(带占位符,运行时填入条件和排序)
// SELECT ... FROM [Commodity] {0} ORDER BY {1} {2}
// OFFSET (@pagesize*(@pageindex-1)) ROWS FETCH NEXT (@pagesize) ROWS ONLY;
// SELECT @total=count(0) FROM [Commodity] {0}
6.3 分页查询
分页需要四个要素:页码、每页条数、排序字段、排序方向。SQL Server 使用 OFFSET...FETCH NEXT 语法,同时用输出参数返回总记录数:
IList<Commodity> result = db.PagerQuery<Commodity>(
c => c.Price > 100, // 查询条件
pageIndex: 1, // 第几页
pageSize: 10, // 每页多少条
out int total, // 输出:总记录数
OrderByType.Asc, // 排序方向
c => c.Id // 排序字段
);
6.4 事务处理
批量新增/修改时,多条 SQL 必须在事务中执行,保证原子性(要么全成功,要么全回滚):
private static int ExecuteTransaction(SqlCommand cmd, Func<SqlCommand, int> func)
{
SqlTransaction trans = cmd.Connection.BeginTransaction();
cmd.Transaction = trans;
try
{
int result = func.Invoke(cmd);
trans.Commit();
return result;
}
catch
{
trans.Rollback(); // 任何一条失败,全部回滚
return 0;
}
}
6.5 SQL 参数化防注入
所有查询条件都使用 SqlParameter 参数化,而不是字符串拼接,从根本上防止 SQL 注入:
// 危险写法(字符串拼接,有注入风险)
string sql = $"SELECT * FROM Commodity WHERE Title='{title}'";
// 安全写法(参数化)
string sql = "SELECT * FROM Commodity WHERE Title=@Title";
cmd.Parameters.Add(new SqlParameter("@Title", title));
七、读写分离与策略模式
7.1 读写分离的思路
生产环境中,数据库通常配置一主多从:主库负责写(INSERT/UPDATE/DELETE),从库负责读(SELECT)。ORM 框架需要根据操作类型自动选择连接。
// 配置主库 + 多个从库
DbProxyOptions options = new DbProxyOptions
{
MasterConnectionString = "Server=主库;...",
SlaveConnectionConfigList = new List<SlaveConnectionConfig>
{
new() { ConnectionString = "Server=从库1;...", HitRate = 20 },
new() { ConnectionString = "Server=从库2;...", HitRate = 20 },
new() { ConnectionString = "Server=从库3;...", HitRate = 20 },
}
};
在 ExecuteSQL 方法中,根据 ReadOrWrite 枚举选择连接:
switch (readOrWrite)
{
case ReadOrWrite.Read:
// 从从库集合中选一个
connectionString = context._IClusterPolicy.GetPolicy(options);
break;
case ReadOrWrite.Write:
// 直接用主库
connectionString = options.MasterConnectionString;
break;
}
7.2 策略模式(Strategy Pattern)
从库选择策略(随机、轮询、权重)是可变的,不能写死在框架里。通过策略模式,把"如何选择从库"这个决策抽象成接口,让使用者自由实现:
// 策略接口
public interface IClusterPolicy
{
string GetPolicy(DbProxyOptions options);
}
// 框架内置:随机策略
public class DefaultClusterPolicy : IClusterPolicy
{
public string GetPolicy(DbProxyOptions options)
{
int index = new Random().Next(0, options.SlaveConnectionConfigList.Count);
return options.SlaveConnectionConfigList[index].ConnectionString;
}
}
// 用户自定义:轮询策略
public class CustomClusterPolicy : IClusterPolicy
{
private static int _num = 0;
public string GetPolicy(DbProxyOptions options)
{
if (_num >= options.SlaveConnectionConfigList.Count) _num = 0;
return options.SlaveConnectionConfigList[_num++].ConnectionString;
}
}
// 使用时注入自定义策略
DbProxyCore db = new CustomDbProxy(options, new CustomClusterPolicy());
这正是开闭原则的体现:框架对扩展开放(可以新增策略),对修改关闭(不需要改框架代码)。
八、构造函数链设计
框架的 DbProxyCore 有三个构造函数,参数从少到多,通过 : this(...) 链式调用,保证最终都走到参数最多的那个构造函数:
// 无参:调用方不传任何配置,框架调用 OnConfiguring 让子类配置
public DbProxyCore() : this(new DbProxyOptions())
{
OnConfiguring(Options);
}
// 传 options:使用默认随机策略
public DbProxyCore(DbProxyOptions option) : this(option, new DefaultClusterPolicy())
{
}
// 参数最全:真正的初始化逻辑在这里
public DbProxyCore(DbProxyOptions option, IClusterPolicy clusterPolicy)
{
Options = option;
_IClusterPolicy = clusterPolicy;
}
设计原则:参数越多的构造函数功能越完整;参数少的构造函数负责提供默认值,然后委托给参数多的构造函数。
九、DbFirst 与 CodeFirst
9.1 DbFirst(数据库先行)
先设计数据库,再通过工具自动生成 C# 实体类。框架通过查询系统表获取表结构,然后用 StringBuilder 拼装 .cs 文件内容并写入磁盘:
// 查询数据库中所有表名
string tableSql = "SELECT [NAME] FROM SYSOBJECTS WHERE XTYPE='u'";
// 查询每张表的字段和类型
string fieldSql = $"SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_Name='{tableName}'";
// 生成 .cs 文件
db.DbFirstCreateClass("D:\\models", "MyApp.Entities");
9.2 CodeFirst(代码先行)
先写 C# 实体类,再通过框架自动创建数据库和表。框架加载指定 DLL,找出所有继承 BaseModel 的类,根据类结构生成建表 SQL 并执行:
// 如果数据库不存在则创建
string sql = $"IF NOT EXISTS(SELECT * FROM sysdatabases WHERE name='{dbName}') CREATE DATABASE [{dbName}]";
// 加载实体所在的 DLL,遍历所有继承 BaseModel 的类,生成建表语句
db.DbCodeFirstInit("MyDatabase", "MyApp.Models.dll");
十、async/await 异步编程
10.1 基本规则
- 方法签名加
async,返回类型改为Task或Task<T> - 方法内部用
await等待异步操作,不阻塞线程 async/await必须成对出现,且会向上传播(调用方也需要await)
// 同步版本
public static T? Find<T>(this DbProxyCore context, int id) where T : BaseModel
{
// ...
conn.Open();
return func.Invoke(cmd);
}
// 异步版本
public static async Task<T?> FindAsync<T>(this DbProxyCore context, int id) where T : BaseModel
{
// ...
await conn.OpenAsync();
return await func.Invoke(cmd);
}
10.2 异步事务
异步版本的事务使用 BeginTransactionAsync、CommitAsync、RollbackAsync:
private static async Task<int> ExecuteTransactionAsync(SqlCommand cmd, Func<SqlCommand, Task<int>> func)
{
DbTransaction trans = await cmd.Connection.BeginTransactionAsync();
cmd.Transaction = (SqlTransaction)trans;
try
{
int result = await func.Invoke(cmd);
await trans.CommitAsync();
return result;
}
catch
{
await trans.RollbackAsync();
return 0;
}
}
十一、扩展方法(Extension Method)
扩展方法让我们可以在不修改原有类的情况下,为其添加新方法。语法要求:静态类中的静态方法,第一个参数用 this 修饰。
// 定义扩展方法
public static class ZxMapManager
{
// 为 MemberInfo 添加 GetName() 方法
public static string GetName(this MemberInfo member)
{
if (member.IsDefined(typeof(ZxAbstractMapAttribute), true))
return member.GetCustomAttribute<ZxAbstractMapAttribute>()!.GetName();
return member.Name;
}
}
// 使用:就像调用实例方法一样
string tableName = typeof(Commodity).GetName();
string colName = prop.GetName();
DbProxy 中大量使用扩展方法,把 Find、Query、Insert 等操作都定义为 DbProxyCore 的扩展方法,既保持了 DbProxyCore 的简洁,又提供了丰富的功能。
十二、partial 类(分部类)
partial 关键字允许把一个类的定义拆分到多个文件中,编译时合并为一个类。在 DbProxy 中,DbProxyCore 的同步方法、异步方法分别放在不同文件里,便于维护:
// DbProxyCore.cs
public abstract partial class DbProxyCore { ... }
// DbProxyCoreNewAsync.cs
public abstract partial class DbProxyCore { ... } // 异步方法
以上内容覆盖了 Zhaoxi.DbProxy ORM 框架从零到完整实现的全部核心知识点。建议先通读"零、框架全貌"建立整体认知,再结合各章节深入理解每个技术点的设计动机。
本文来自博客园,作者:龙猫•ᴥ•,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/19757851

浙公网安备 33010602011771号