通常的 ORM 实现基于配置或注释, 由反射或 Emit 生成相应的 Sql 语句, 然后将 Sql 发送给数据库解析 Sql 字符串生成 AST 再交给优化器处理后执行, 返回的数据再经由反射或 Emit 转换为相应的实体实例. 作者认为上述方式主要存在以下两个问题:
实体类代码是硬编码的, 如果实体类定义变更必须重新编译应用再部署, 不利于实现运行时动态变更实体定义;
CRUD 操作转换为 Sql 的实现复杂, 且需要针对不同的数据库做适配优化.
由于作者追求极致简单的系统架构以及丝般顺滑的开发体验, 所以作者采用了另类的方式在框架内实现了 ORM, 之于另类在什么地方我们先通过一些简单示例后再来说明一下实现原理.
一, CRUD 操作
还是用系统自带的 Emploee 模型作为示例, 在 IDE 新建服务模型添加以下代码保存发布后可通过主菜单 ->Service->Invoke 运行测试:
- // 事务新建两条记录
- var emp1 = new Entities.Emploee();
- emp1.Name = "Rick";
- emp1.Account = "rick@appbox.dev";
- emp1.Birthday = new DateTime(1977, 3, 16);
- var emp2 = new Entities.Emploee();
- emp2.Name = "Johne";
- emp2.Account = "johne@appbox.dev"
- emp2.Birthday = new DateTime(1979, 1, 2);
- var txn = await Transaction.BeginAsync();
- try {
- await EntityStore.SaveAsync(emp1, txn);
- await EntityStore.SaveAsync(emp2, txn);
- await txn.CommitAsync();
- } catch (Exception ex) {
- txn.Rollback();
- }
- // 查询记录
- var q = new TableScan<Entities.Emploee>();
- q.Filter(t => t.Name == "Rick");
- var emps = await q.ToListAsync();
- // 更新记录
- emps[0].Name = "Rick Lu";
- await EntityStore.SaveAsync(emps[0]);
- // 删除记录
- await EntityStore.DeleteAsync(emps[0]);
以上只是已实现的一些 API 示例, 复杂的如根据索引查询, 聚合查询等 API 正在设计开发中.
二, 实现原理
设计时:
这部分实现重度依赖 Roslyn 功能, 服务端在开发人员登录至 IDE 后会通过 Roslyn 生成虚拟项目.
实体模型保存时服务端生成虚拟的实体类代码, 并加入虚拟项目内;
服务模型保存时服务端生成虚拟的服务类代码, 并加入虚拟项目内; 发布时服务端的编译引擎解析虚拟的服务代码, 然后转换为运行时服务代码, 再通过 Roslyn 编译为动态服务组件. 其中虚拟代码转换为运行时代码的过程主要是:
将实体属性取赋值操作 (eg: entity.Name = "aa" 或 var temp = entity.Name) 转换为针对运行时 Entity 类的 GetXXX()及 SetXXX()操作. 这里需要注意的是运行时只存在一个 Entity 类(类似于 KVO 通过字典表保存属性值);
将查询条件转换为可序列化的表达式, 这样存储引擎执行查询命令时可委托 clr emit 生成过滤指令, 存储引擎在扫描时直接使用过滤指令计算满足条件的记录.
运行时:
这部分实现参考以下流程图, 需要注意的是存储引擎是基于 RocksDB 的, 实体数据转换为 KV 数据(如 Key=Id, Value=[字段标识: 字段值; 字段标识: 字段值]), 这样转换过程就不需要使用反射或 Emit.
三, 性能测试
作者做了简单的性能测试(单节点 I74C8G 虚拟机):
并发插入不带索引不带外键的简单实体约 28000tps;
通过惟一索引查询约 80000qps;
带条件扫描少量记录约 35000qps.
四, 查询限制
存储引擎不支持 join, 可通过分别查询出数据后利用 Linq 做 join 操作;
存储引擎暂只支持根据 Entity.Id 或指定索引查询排序, 不支持自定义排序.
五, 本篇小结
本篇主要介绍了框架集成的 ORM 的另类实现, GitHub https://github.com/enjoycode/appbox.deploy 上的运行时已经更新可测试. 如果您有问题或 Bug 报告, 请留言或在 GitHub https://github.com/enjoycode/appbox.deploy 提交 Issue.
来源: https://www.cnblogs.com/BaiCai/p/10953149.html