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,返回类型改为 TaskTask<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 异步事务

异步版本的事务使用 BeginTransactionAsyncCommitAsyncRollbackAsync

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 中大量使用扩展方法,把 FindQueryInsert 等操作都定义为 DbProxyCore 的扩展方法,既保持了 DbProxyCore 的简洁,又提供了丰富的功能。


十二、partial 类(分部类)

partial 关键字允许把一个类的定义拆分到多个文件中,编译时合并为一个类。在 DbProxy 中,DbProxyCore 的同步方法、异步方法分别放在不同文件里,便于维护:

// DbProxyCore.cs
public abstract partial class DbProxyCore { ... }

// DbProxyCoreNewAsync.cs
public abstract partial class DbProxyCore { ... } // 异步方法

以上内容覆盖了 Zhaoxi.DbProxy ORM 框架从零到完整实现的全部核心知识点。建议先通读"零、框架全貌"建立整体认知,再结合各章节深入理解每个技术点的设计动机。

posted @ 2026-03-23 14:56  龙猫•ᴥ•  阅读(0)  评论(0)    收藏  举报