16-C#.Net-自研ORM框架-学习笔记

C# 数据库访问与ORM框架全面指南:从ADO.NET到Entity Framework Core 在现代应用开发中,数据持久化是核心需求之一。作为.NET生态系统中的主力语言,C#提供了丰富多样的数据库访问技术和工具。本文将全面探讨C#中的数据库访问方式,重点介绍三种主流ORM(对象关系映射)框架:Entity Framework Core、Dapper和NHibernate,帮助开发者根据项目需求做出合理的技术选型。 阅读详情

本文记录从零手写一个 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 : BaseModelT 必须继承 BaseModel(基类约束)
where T : IInterfaceT 必须实现某接口(接口约束)
where T : new()T 必须有无参构造函数
where T : classT 必须是引用类型
where T : structT 必须是值类型

在 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 框架从零到完整实现的全部核心知识点。建议先通读"零、框架全貌"建立整体认知,再结合各章节深入理解每个技术点的设计动机。

C#学习笔记 使用ORM框架SqlSugar 由果糖团队维护的一款开源DotNet ORM框架SqlSugar .Net ORM 5.X 官网 、文档、教程 - SqlSugar 5x - .NET果糖网 (donet5.com) 阅读详情

相关推荐

C#与.NET核心知识测验

本文包含一系列关于C#语言、.NET运行时、ORM、泛型、异步编程、单元测试及设计模式的精选问题与解答,涵盖语法特性、内存管理、依赖注入和数据库交互等关键主题,适合开发者巩固基础知识。

day7的博客 827

.NET常用的ORM框架及性能优劣分析总结

• Entity Framework:适合大多数 .NET 项目,特别是需要快速开发和维护的场景。• Dapper:适合需要高性能和灵活查询的场景,尤其是高并发应用。• NHibernate:适合需要复杂映射和高级功能的大型项目。• LLBLGen Pro:适合需要专业级功能和性能优化的商业项目。• NPoco 和 RepoDb:适合需要轻量级和高性能的场景,介于 Dapper 和 EF 之间。

shenjqiang的专栏 2949

.NET 主流ORM框架功能介绍

Entity Framework是微软官方推荐的ORM工具,它支持代码优先(Code First)、数据库优先(Database First)和模型优先(Model First)等多种开发模式,让你能以高度抽象的方式操作数据库。

java专栏 1384

C#中的ORM框架

1. 什么是ORM ORM:对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping) 用于实现面向对象编程语言里不同类型系统的数据之间的转换 ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象与关系数据库相互映射 ORM可以解决数据库与程序间的异构性,比如:在C#中我们使用string表示字符串,而Oracle中可使用varchar2,MySQL中可使用varchar,SQLServer可使用nvarchar。 如果没有ORM我们原始

XiaoCai 9514

FPGA开发者必看:如何用SATA3.0主机控制器IP实现高速存储扩展(附Xilinx实测数据)

本文为FPGA开发者提供了基于SATA3.0主机控制器IP核的高速存储方案设计与优化指南。通过详细解析IP核架构、Xilinx FPGA平台集成实战及性能优化技巧,帮助开发者实现接近理论极限的540MB/s数据传输速度。文章还包含常见问题解决方案和高级应用场景拓展,是硬件工程师提升存储性能的必备参考。

weixin_30632089的博客 114

ADAS系统架构与核心功能解析:从感知到执行的智能驾驶方案

高级驾驶员辅助系统(ADAS)是现代汽车智能化的核心技术,其本质是通过传感器融合、算法决策与车辆控制,构建一个“感知-决策-执行”的闭环系统。在技术原理上,ADAS依赖摄像头、毫米波雷达、激光雷达等多源传感器进行环境感知,并通过深度学习模型与融合算法(如卡尔曼滤波)构建精准的环境模型。其技术价值在于提供主动安全冗余与驾驶舒适性,核心应用场景包括自动紧急制动(AEB)、自适应巡航(ACC)及车道保持辅助(LKA)等。随着传感器成本下降与域控制器算力提升,ADAS正从高端配置向主流车型快速普及,并逐步向高阶导航

weixin_31062533的博客 414

16个值得推荐的.NET ORM框架

ORM 是 Object Relational Mapping 的缩写,译为“对象关系映射”,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。它解决了对象和关系型数据库之间的数据交互问题,ORM的作用是在关系型数据库和业务实体对象之间作一个映射,这样我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 2283

白话详细解读(七)----- CBAM:Convolutional Block Attention Module

一、CBAM简介 论文链接:https://x.sci-hub.org.cn/scholar?hl=zh-TW&as_sdt=0%2C5&q=CBAM%3A+Convolutional+Block+Attention+Module&btnG=&oq=CB CBAM这篇论文是在2018年提出的,自提出之后,已经被引用上千次,可见其影响力。CBAM是一种专门为了卷积神经网络而设计的注意力模块,传统的卷积存在只关注局部信息,而往往忽略全局信息的弊端,以3*3卷积为例,滤波器有9个

奋斗の博客 1万+

C#/.NET/.NET Core技术前沿周刊 | 第 30 期(2025年3.10-3.16)

C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等。docs/DotNet/DotNetWeekly.md · 追逐时光者/DotNetGuide - Gitee.comADO.NET中SQL绑定变量方式总结。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 1507

【2023】Java利用modbus4j实现工业设备Modbus TCP数据采集与解析

本文详细介绍了在2023年使用Java和modbus4j库实现工业设备Modbus TCP数据采集与解析的完整实践。内容涵盖开发环境搭建、TCP连接建立、开关量与模拟量数据的读取与解析、批量读取性能优化、控制指令写入以及常见错误排查指南,为开发者构建稳定高效的工业物联网数据采集系统提供全面指导。

rose2的博客 722

SAP Retail 补货里的 Requirement Groups,站点和商品太多时,主数据维护如何做减法

SAP Retail补货主数据设计中,Requirement Groups(需求组)是常被低估但至关重要的分类机制。它通过将具有相同补货需求的接收方(如门店)按商品分类(Merchandise Category)分组,大幅降低主数据维护量。在大型零售场景中(如800家门店、6万商品),直接维护每个门店和商品的组合参数会导致维护量失控。Requirement Groups允许企业按品类特性(如乳制品、饮料等)为不同门店组设置统一补货参数,并随业务变化灵活调整。该机制与商品分类紧密绑定,不同于MRP Group

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 67

C# orm实体映射框架sqlsugar使用记录

C#中轻量级的orm框架sqlsugar,用法很简单,官网http://www.codeisbug.com/ github:https://github.com/sunkaixuan/SqlSugar 世界上最简单的ORM,只需要配置连接字符串,F5运行控制台自动建库建表运行DEMO Using SqlSugar is very simple ,And it's powerful. SqlSugar=One object+One parameter=16 functions, Support:My

Zy100Papa的博客 2697

使用R语言获取data.frame中指定列的数据

这里以"df"为例,包含三列数据:“列1”、“列2"和"列3”。通过以上代码,我们成功获取了"列1"和"列3"的数据,并将它们分别存储在了变量"column1"和"column3"中。假设我们有一个名为"df"的data.frame,其中包含了多个列,我们想要提取其中的某一列。符号,我们可以从data.frame中提取出"列2"的数据,并将其赋值给变量"column2"。符号,我们可以轻松提取出data.frame中特定列的数据,并将其存储在变量中供进一步的分析和处理使用。假设我们要获取"列2"的数据。

DevRevolt的博客 2321

C#/.NET/.NET Core技术前沿周刊 | 第 6 期(2024年9.16-9.22)

C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿,推荐或自荐优质文章/项目/学习资源等。每周一定期发布!!!📰C#/.NET/.NET Core技术前沿周刊汇总🏆C#/.NET/.NET Core技术前沿周刊Issues(投稿地址)

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 1813

【05】昊一源科技——昊一源科技 嵌入式笔试, 校招,题目记录及解析

本文记录了昊一源科技嵌入式校招笔试题目及解析,涵盖计算机基础、汇编语言、网络协议、C语言编程等内容。主要包括:1)补码真值计算;2)MCS51汇编指令执行后寄存器状态;3)网络广播地址计算;4)TCP协议特点;5)C语言三目运算符应用;6)宏定义优先级问题;7)字符指针与数组的区别;8)C++静态成员变量应用;9)三位完全平方数查找程序。题目难度适中,涉及嵌入式开发常见知识点,对求职者基础能力考察全面。

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 1307

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

C# SqlSugar框架的学习使用(一)--SqlSugar简介及创建

前言最近有个开发项目准备启动,由于要求的开发周期较短,所以准备用个C#的框架,原本最初考虑用成熟的EF框架,但是觉得那个框架也太重了,就在网上找找了别的,无意间发现了SqlSugar的框...

Vaccae的博客 1万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

【转】.NET框架简介

.NET 框架是由微软开发的软件开发平台,其最主要的两个组成部分是公共语言运行时 (CLR) 和框架类库 (FCL),基础类库 (BCL)是框架类库的一个子集。 .NET 框架简介 下图展示了 .NET 框架的主要结构。 其中,最下层的无疑就是操作系统了。 在 .NET 框架中,运行于底层的是公共语言运行时 (CLR),它是公共语言基础设施 (CLI) 的一个实现。 在 CLR 之上,.NET 框架提供了全面、丰富的框架类库 (FCL),这些类库通过 CLR 调用操作系统的 API 实现各种.

sinolover的专栏 3706

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

C# ORM轻量框架--SqlSugar的学习使用

最近有个开发项目准备启动,由于要求的开发周期较短,所以准备用个C#的框架,原本最初考虑用成熟的EF框架,但是觉得那个框架也太重了,就在网上找找了别的,无意间发现了SqlSugar的框架。简单试了下,发现真是的方便,入门也简单。稳定性和技术支持, 虽然不是官方ORM, 但在稳定性上也是有着数年用户积累,如果遇到问题可以在GITHUB提出来,会根据紧急度定期解决。高性能 ,不夸张的说,去掉Sql在数据库执行的时间,SqlSugar是EF数倍性能,另外在批量操作和一对多查询上也有不错的SQL优化。

Blue 650

C# .NET ORM 框架 SqlSugar 5.X 版本

SqlSugar 5.X API 使用SqlSugar非常简单,而且功能强大。 SqlSugar=一个对象+一个参数=16个函数, 支持数据库:MySql、SqlServer、Sqlite、Oracle 、 postgresql SqlSugar 16个功能 创建SqlSugarClient 所有操作都基于SqlSugarClient SqlSugarClient参数...

ejinxian的专栏 5455

你可能遗漏的一些C#/.NET/.NET Core知识点

在这个快速发展的技术世界中,时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节,以帮助大家更全面地了解这些技术栈的特性和发展方向。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 2798

.NET 面试题 - C#基础

http://6.ASP.NET MVC,刚开始接触这个技术的时候我经常不理解他们的名字,我相信许多学http://ASP.NET开发人员开始接触MVC应该也和我一样产生很多为什么,也会误认为认为MVC与http://ASP.NET完全没有关系,是一个全新的Web开发,深入学习后我才知道事实上http://ASP.NET是创建WEB应用的框架而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系,所以可以称之为http://ASP.NET MVC。功能内聚是最强的内聚,其优点是它的功能明确。

微软Dynamics 365 CRM,Power Apps,Power Platform技术和技能培训 2643

自己动手写ORM框架

提起ORM框架,大家都很熟悉,网上流行的ORM框架有很多,其中出名的有一些,不出名的更是数不胜数。 下面是自己实现的一个简单的ORM框架,实现了常用的增删查改功能,供大家研究ORM实现原理。 功能描述: 1.支持Update语句 2.支持Insert语句 3. 支持Delete语句 4.支持事务 5.支持自定义增删查改语句 6.支...

HJW1979的博客 269

Dos.ORM使用教程

Dos.C#.Net使用   Dos.ORM(原Hxj.Data)于2009年发布,并发布实体生成工具。在开发过程参考了多个ORM框架,特别是NBear,MySoft、EF、Dapper等。吸取了他们的一些精华,加入自己的新思想。该组件已在上百个成熟企业项目中应用 为什么选择Dos.ORM(原Hxj.Data)? 上手简单,0学习成本。使用方便,按照sql书写习惯编写C#.NE...

547

ORM for Net主流框架汇总与效率测试

ORM for Net主流框架汇总与效率测试 框架已经被越来越多的人所关注与使用了,今天我们就来研究一下net方面的几个主流ORM框架,以及它们的效率测试(可能会有遗漏欢迎大家讨论)。 ORM框架:Object/Relation Mapping(对象/关系 映射)的缩写,易于理解的模型化数据的方法。简单的说就是把数据库的关系型数据类型转换为用对象型程序控制的框架类型。

rise51的专栏 2569
上一篇: 15-C#.Net-编程思想(POP-OOP-AOP-DDD)-学习笔记
似水明俊德
博客等级 码龄8年 247粉丝 276原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值