驰骋 BPM ORM 设计 --- 技术报告
文档版本:2026-06
章节编号:1.1
依据代码:
CCFlow/Components/BP.En30(核心 ORM 组件)说明:本文基于源码静态分析撰写,用于技术分享与方案论证,力求准确描述设计思想并客观分析优劣。
一、缘起与设计目标
1.1 起始于 2002 年
驰骋 ORM 设计起始于 2002 年 ,起因是探索可复用的企业应用框架技术。当时需要一套能够:
- 统一描述「数据表 ↔ 实体类」的映射关系;
- 在多种数据库(SQL Server、Oracle、MySQL 等)上复用同一套业务代码;
- 支撑快速搭建管理类应用(增删改查、列表查询、主从表编辑)。
EntityDBAccess.cs 文件头注释仍保留着创建时间标记:
1:5:CCFlow/Components/BP.En30/En/EntityDBAccess.cs
/*
简介:负责存取数据的类
创建时间:2002-10
最后修改时间:2002-10
*/
1.2 设计目标(与市面 ORM 的差异点)
驰骋 ORM 从一开始就不是「纯数据访问层」,而是面向企业低代码应用的元数据框架:
| 维度 | 驰骋 ORM 目标 | 典型 ORM(EF Core / Hibernate)目标 |
|---|---|---|
| 核心对象 | 表 + 字段 + 控件 + 关系 | 类 + 属性 + 导航属性 |
| 元数据载体 | Map / Attr / Attrs(代码 + 库表双轨) |
特性标注 / Fluent API / 约定 |
| 产出物 | CRUD + 表单 UI + 查询面板 + 主从/多对多 | CRUD + LINQ 查询 |
| 运行方式 | 属性值存于 Row(Hashtable),弱类型读写 |
强类型属性 + 变更跟踪 |
这一取向决定了后续 CCFlow、JFlow、低代码表单、组织结构等模块均可在同一套元数据上叠加能力,而不必重复定义字段语义。
二、演进历程:XML → Map / Attr / Attrs
2.1 第一阶段:XML 描述映射(约 2002)
早期版本用 XML 文件 表达数据表与实体类的完整关系。Map.GenerMap(string xml) 方法至今仍保留这一能力:读取 XML 中的 Base、Attr、SearchAttr、Dtl、Dot2Dot 等节点,动态构建 Map 对象。
743:773:CCFlow/Components/BP.En30/En/MapExt/Map.cs
#region 生成属性根据xml.
private string PKs = "";
public void GenerMap(string xml)
{
DataSet ds = new DataSet("");
ds.ReadXml(xml);
foreach (DataTable dt in ds.Tables)
{
switch (dt.TableName)
{
case "Base":
this.DealDT_Base(dt);
break;
case "Attr":
this.DealDT_Attr(dt);
break;
case "SearchAttr":
this.DealDT_SearchAttr(dt);
break;
case "Dtl":
this.DealDT_SearchAttr(dt);
break;
case "Dot2Dot":
this.DealDT_Dot2Dot(dt);
break;
default:
throw new Exception("XML 配置信息错误,没有约定的标记:" + dt.TableName);
}
}
// 检查配置的完整性。
}
XML 方案的优点是映射与代码解耦、便于工具生成;缺点是调试困难、版本管理成本高、缺乏编译期检查。
2.2 第二阶段:Map / Attr / Attrs 类(约 2003)
2003 年 ,架构改造为以 Map、Attr、Attrs 三个核心类在 C# 代码中描述映射,成为至今沿用的主路径。
| 类 | 职责 |
|---|---|
Map |
描述一个实体(En)的完整元数据:物理表名、主键策略、字段集合、从表、多对多、查询条件、关联方法等 |
Attr |
描述单个字段/属性:键名、物理列名、数据类型、字段逻辑类型、UI 控件、可见性、默认值、外键绑定等 |
Attrs |
Attr 的集合,提供 AddTBString、AddTBInt、AddDDLEntities、AddDDLSysEnum 等工厂方法,简化 Map 构建 |
实体子类通过重写 EnMap 属性返回自己的 Map 定义。以组织结构中的 Emp(用户)为例:
346:382:CCFlow/Components/BP.En30/Port/Emp.cs
public override Map EnMap
{
get
{
if (this._enMap != null)
return this._enMap;
Map map = new Map("Port_Emp", "用户");
#region 基本属性
map.EnDBUrl = new DBUrl(DBUrlType.AppCenterDSN);
map.IndexField = EmpAttr.FK_Dept;
#endregion
#region 字段
map.AddTBStringPK(EmpAttr.No, null, "编号", true, false, 1, 50, 30);
// ...
map.AddTBString(EmpAttr.Name, null, "名称", true, false, 0, 200, 30);
map.AddDDLEntities(EmpAttr.FK_Dept, null, "部门", new BP.Port.Depts(), false);
// ...
#endregion 字段
代码化 Map 带来了编译期类型安全(字段常量类如 EmpAttr)、IDE 跳转、与业务逻辑同仓版本管理等优势,XML 路径退居兼容与工具导入用途。
三、核心架构
3.1 类层次与职责
目录结构(BP.En30):
| 目录/文件 | 说明 |
|---|---|
En/ |
实体基类、Map/Attr/Attrs、SqlBuilder、QueryObject、EntityDBAccess |
DA/ |
数据库访问、DBAccess、DataType、缓存 |
Sys/ |
低代码表单元数据:MapAttr、MapData、MapDtl、SFTable、SFDBSrc |
Port/ |
组织结构实体(Emp、Dept、Station 等),均基于本 ORM 定义 |
3.2 数据承载:Row 弱类型模型
实体字段值不直接映射为 C# 自动属性,而是存放在 Row(继承 Hashtable) 中,通过 GetValByKey / SetValByKey 读写:
13:42:CCFlow/Components/BP.En30/En/Row.cs
public class Row : Hashtable
{
// ...
public void LoadAttrs(Attrs attrs)
{
this.Clear();
foreach (Attr attr in attrs)
{
switch (attr.MyDataType)
{
case DataType.AppInt:
// ...
设计意图:
- 低代码场景下字段可运行时增删,无需重新编译实体类;
- 主表、从表、流程表单共用同一套读写 API;
- 业务类仍可封装强类型属性(如
Emp.Name),内部转调GetValStringByKey。
3.3 持久化流水线
| 组件 | 职责 |
|---|---|
SqlBuilder |
根据 Map/Attrs 生成 SELECT/INSERT/UPDATE/DELETE,处理多主键、多数据库方言 |
SQLCache |
缓存实体级 SQL 模板,减少重复拼接 |
EntityDBAccess |
执行 SQL、异常时触发 CheckPhysicsTable 自动建表/扩列 |
QueryObject |
链式构造 WHERE/ORDER BY,供 Entities 批量查询 |
3.4 主键策略
通过实体基类约定主键字段名:
| 基类 | 主键 | 典型场景 |
|---|---|---|
EntityNoName |
No(字符串) |
部门、用户、角色、流程编号 |
EntityOID |
OID(自增整型) |
运行实例、从表明细 |
EntityMyPK |
MyPK(组合字符串) |
DeptEmp、DeptEmpStation 等关联表 |
FieldType 枚举进一步区分字段逻辑角色:普通、主键、外键、枚举、主键+外键、关联文本、虚拟字段等。
四、超越字段映射:BP 扩展能力
原始 ORM 仅解决「数据库列 ↔ 实体属性」映射;驰骋 BP 层在此基础上扩展为应用元数据框架。
4.1 字段逻辑类型(FieldType / FieldTypeS)
141:197:CCFlow/Components/BP.En30/En/EnumLab.cs
public enum FieldTypeS
{
Normal = 0, // 普通属性字段
Enum = 1, // 枚举字段
FK = 2 // 外键字段
}
public enum FieldType
{
Normal, PK, FK, Enum, PKFK, PKEnum, RefText, NormalVirtual, MultiValues
}
| 类型 | 含义 | 典型 API |
|---|---|---|
| 属性字段 | 普通列,文本框/数值/日期 | AddTBString、AddTBInt、AddTBDate |
| 枚举字段 | 绑定 Sys_Enum 或字符串枚举 |
AddDDLSysEnum |
| 外键字段 | 绑定另一 Entities 集合 |
AddDDLEntities |
| 外部数据源字段 | 绑定 SFTable(SQL/WS/Handler 等) |
UIBindKey → SFTable |
| 关联文本 | 外键旁的冗余显示列(如 FK_Dept + FK_DeptT) |
自动追加 key + "T" 的 Attr |
Attr.ToMapAttr 与 MapAttr.HisAttr 构成代码 Map ↔ 库表 MapAttr 双向转换桥梁,使设计态(设计器保存到 Sys_MapAttr)与运行态(GenerMap() 动态生成 Map)保持一致。
4.2 控件类型映射(UIContralType)
Attr 不仅记录 MyDataType(存储类型),还记录 UIContralType(呈现控件):
- 基础:
TB(文本框)、DDL(下拉)、CheckBok、RadioBtn - 流程/公文:
HandWriting(签名)、FrmImgAth(图片附件)、GovDocFile(正文)、JobSchedule(进度图) - 移动端:
MapPin、MobilePhoto、Location
低代码表单设计器将控件配置写入 Sys_MapAttr,运行时 MapDtl.GenerMap() 遍历 MapAttrs 调用 map.AddAttr(mapAttr.HisAttr) 合成完整 Map,从而一份元数据同时驱动持久化与 UI 渲染。
4.3 主表---从表映射(EnDtl)
Map.AddDtl 声明主实体下的从表集合:
516:525:CCFlow/Components/BP.En30/En/MapExt/Map.cs
public void AddDtl(Entities ens, string refKey, string groupName = null, DtlEditerModel model = DtlEditerModel.DtlBatch, string icon = null)
{
EnDtl dtl = new EnDtl();
dtl.Ens = ens;
dtl.RefKey = refKey;
dtl.GroupName = this.currGroupMethodName;
dtl.DtlEditerModel = model;
dtl.Icon = icon;
this.Dtls.Add(dtl);
}
refKey:从表中外键列(如FK_Node);DtlEditerModel:批量编辑、查询编辑、自定义 URL 等模式;- 流程表单明细(
MapDtl)、组织结构子面板均基于此机制。
4.4 一对多 / 多对多映射(AttrOfOneVSM)
Map.AttrsOfOneVSM 描述「一」端实体与「多」端实体通过中间表(MM 表)的关联,例如用户维护所属部门:
405:407:CCFlow/Components/BP.En30/Port/Emp.cs
map.AttrsOfOneVSM.AddBranches(new DeptEmps(), new BP.Port.Depts(),
BP.Port.DeptEmpAttr.FK_Emp,
BP.Port.DeptEmpAttr.FK_Dept, "部门维护", EmpAttr.Name, EmpAttr.No, "@WebUser.DeptNo");
支持树形部门、平铺面板等多种维护 UI(Dot2DotModel),直接生成部门-人员维护界面,无需手写关联 CRUD。
4.5 外部数据源(SFTable / SFDBSrc)
SFTable:定义下拉/列表的外部数据来源(本库 SQL、跨库、WebService、Java 处理器等);SFDBSrc:多数据源连接配置,使Map可指定DBSrc指向非本地库;- 字段通过
UIBindKey关联SFTable,实现不建物理外键列也能绑定外部数据的下拉选项。
4.6 自动 DDL(CheckPhysicsTable)
实体在 Insert/Update 失败时可触发 CheckPhysicsTable():按 Map.Attrs 定义自动创建表或扩展列长。这使低代码「先画表单、后建表」成为可能,也是与「迁移脚本驱动」的 ORM 的显著差异。
4.7 查询与权限扩展
Map 还承载:
SearchNormals/SearchFKEnums:列表页查询条件;HisRefMethods:实体关联功能(修改密码、部门角色维护等);UAC:插入/删除/更新/查看权限;AddHidden:数据范围过滤(如 SAAS 模式OrgNo = @WebUser.OrgNo)。
五、生态承载:基于 ORM 构建的产品模块
驰骋 ORM 不是孤立的数据层,而是 CCFlow / JFlow / 驰骋 BPM 低代码平台 的统一元数据底座。
| 模块 | 与 ORM 的关系 |
|---|---|
| 驰骋 BPM / CCFlow | 流程模板、节点、表单均用 Entity/Entities 定义;运行期表单数据通过 GEEntity(通用实体)+ 动态 GenerMap() 读写 |
| JFlow | Java 版移植同一套 Map/Attr 语义,保持跨语言元数据一致 |
| 低代码 | 设计器操作 Sys_MapData/Sys_MapAttr;运行时合成 Map;主从表、外键、枚举、外部数据源均落在 Attr 扩展属性上 |
| 组织结构 | Port_* 五表实体均在 BP.Port 用 EnMap 声明;DeptEmp、DeptEmpStation 用 EntityMyPK;人员界面用 AttrsOfOneVSM 维护多部门 |
| 权限 / 报表 / 接口 | 统一 GetValByKey 读写;QueryObject 构造列表;ToJsonObject() 输出 API |
结论:更换 ORM 意味着重写元数据模型、表单设计器、流程引擎数据层、组织结构集成接口------成本极高,因此该 ORM 是平台级基础设施而非可插拔组件。
六、与市面 ORM 的对比评估
6.1 对比总表
| 维度 | 驰骋 ORM(BP.En30) | Entity Framework Core | Hibernate / JPA | Dapper |
|---|---|---|---|---|
| 定位 | 元数据 + ORM + UI 描述 | 标准 .NET ORM | Java 标准 ORM | 微 ORM / SQL 映射 |
| 映射定义 | Map/Attr 代码 + Sys_MapAttr 库表 |
特性 / Fluent API | 注解 / XML | 手写 SQL |
| 查询方式 | QueryObject + SqlBuilder |
LINQ | HQL / Criteria / JPQL | 原始 SQL |
| 变更跟踪 | 无(显式 Update) | 有 | 有(Session) | 无 |
| UI 元数据 | 内置(控件、布局、从表) | 无 | 无 | 无 |
| 主从 / 多对多 | EnDtl / AttrOfOneVSM 一等公民 |
导航属性配置 | 关联映射 | 自行实现 |
| 自动建表 | CheckPhysicsTable |
Migrations | hbm2ddl |
无 |
| 多数据库 | SqlBuilder 方言分支 |
提供商插件 | 方言 | 取决于 SQL |
| 低代码友好 | 极高 | 低 | 低 | 低 |
| 强类型开发体验 | 中(Row + 部分封装属性) | 高 | 高 | 高 |
| 学习曲线(纯 CRUD) | 中(需理解 Map 体系) | 中 | 中高 | 低 |
6.2 驰骋 ORM 的优势
- 元数据一体 :同一
Attr描述列类型、控件、外键、枚举、默认值,设计器与运行引擎零阻抗。 - 企业功能内建:主从表、多对多维护、列表查询、权限位、关联方法,减少重复劳动。
- 低代码原生 :运行期可
GenerMap(),字段可来自数据库配置,适合用户自定义表单。 - 久经生产验证:2002 年至今,与 CCFlow 流程场景深度耦合(附件、签名、公文、轨迹等控件类型)。
- 多数据库与国产库 :
EntityDBAccess内可见 Oracle、MySQL、PostgreSQL、达梦、人大金仓等分支。
6.3 驰骋 ORM 的局限
- 非标准 ORM:无法直接用于普通 .NET 项目的「引包即用」,与 EF Core 生态(LINQ、迁移、工具链)不兼容。
- 弱类型 Row:大型团队纯代码开发时,缺少编译期字段检查,重构依赖全局搜索。
- 无现代查询抽象 :没有 LINQ / IQueryable,复杂查询依赖
SqlBuilder字符串,可读性与组合性弱于 EF。 - 无变更跟踪 :更新需显式调用
Update()并指定字段,易遗漏或误更新。 - XML 遗产 :
GenerMap(xml)与代码EnMap双路径并存,增加维护心智负担。 - 测试与 Mock :
Entity与DBAccess静态耦合紧密,单元测试需依赖数据库或大量封装。
6.4 适用场景判断
| 场景 | 建议 |
|---|---|
| 驰骋 BPM / CCFlow / JFlow 二次开发 | 必须使用本 ORM,无替代 |
| 低代码表单 + 流程 + 组织结构一体部署 | 最适合 |
| 全新 .NET 业务系统、与 BPM 无关 | 不推荐,应选 EF Core + 独立 DTO |
| 仅需高性能只读查询 | 可在 Entities 层旁路 DBAccess.RunSQL,但失去元数据收益 |
| 与外部 ORM 共存 | 仅建议边界清晰的数据同步,避免同一表双映射 |
七、典型开发模式示例
7.1 声明式实体(代码优先)
csharp
// 1. 字段常量类
public class XxxAttr { public const string Title = "Title"; }
// 2. 实体类
public class Xxx : EntityNoName
{
public override Map EnMap
{
get
{
if (_enMap != null) return _enMap;
Map map = new Map("My_Xxx", "示例");
map.AddTBStringPK(XxxAttr.No, null, "编号", true, false, 1, 50, 20);
map.AddTBString(XxxAttr.Title, null, "标题", true, false, 0, 200, 30);
map.AddDDLSysEnum("Status", 0, "状态", true, false, "XxxStatus");
_enMap = map;
return _enMap;
}
}
}
// 3. 集合类
public class Xxxs : EntitiesNoName
{
public override Entity GetNewEntity => new Xxx();
}
7.2 运行时使用
csharp
Xxx en = new Xxx();
en.No = "001";
en.SetValByKey(XxxAttr.Title, "测试");
en.Insert();
Xxxs ens = new Xxxs();
ens.RetrieveByAttr("Status", 1);
Xxx en2 = new Xxx("001");
en2.RetrieveFromDBSources();
string title = en2.GetValStringByKey(XxxAttr.Title);
7.3 低代码动态表单(元数据优先)
- 设计器保存字段 →
Sys_MapAttr; MapDtl.GenerMap()/MapData.GenerMap()合成Map;GEDtl/GEEntity作为通用实体承载数据;- 前端根据
MapAttrJSON 渲染控件。
八、总结
驰骋 ORM 是一段从 2002 年 XML 映射 演进到 2003 年 Map/Attr/Attrs 代码化元数据 、并在二十余年中持续扩展为低代码应用框架底座的技术路线。
它的本质不是「对象---关系映射库」,而是:
用统一的
Map元数据,同时描述数据库结构、实体行为、UI 控件、主从与多对多关系、外部数据源与查询条件,并驱动 CCFlow、JFlow、组织结构与低代码表单的全生命周期。
与 Entity Framework、Hibernate 等市面 ORM 相比,驰骋 ORM 在标准性、强类型、现代查询 上不占优,但在低代码、流程、表单、组织一体化场景具有不可替代的语义内聚优势。理解这一边界,是正确使用和扩展驰骋 BPM 平台的前提。
附录:核心类型索引
| 类型 | 路径 | 说明 |
|---|---|---|
Map |
En/MapExt/Map.cs |
实体元数据根对象 |
Attr / Attrs |
En/MapExt/Attr.cs |
字段元数据与集合 |
Entity / Entities |
En/Entity.cs、En/Entities.cs |
实体与集合基类 |
Row |
En/Row.cs |
字段值容器 |
SqlBuilder |
En/SqlBuilder.cs |
SQL 生成 |
EntityDBAccess |
En/EntityDBAccess.cs |
持久化执行 |
QueryObject |
En/QueryObject.cs |
查询构造器 |
EnDtl / AttrOfOneVSM |
En/EnDtl.cs、En/MapExt/AttrOfOneVSM.cs |
从表与多对多 |
MapAttr |
Sys/MapAttr.cs |
低代码字段元数据(库表) |
MapData / MapDtl |
Sys/MapData.cs、Sys/MapDtl.cs |
表单/从表定义 |
SFTable / SFDBSrc |
Sys/SFTable.cs、Sys/SFDBSrc.cs |
外部数据源 |