不走寻常路的 ORM:Map/Attr 元数据体系如何同时驱动持久化与表单渲染

与市面 ORM 不同,这套设计把字段类型、控件、外键、查询条件都装进同一份元数据。本文拆解它的类层次、持久化流水线与扩展机制,并客观分析优劣。

一、缘起与设计目标

1.1 起始于 2002 年

驰骋 ORM 设计起始于 2002 年 ,起因是探索可复用的企业应用框架技术。当时需要一套能够:

  • 统一描述「数据表 ↔ 实体类」的映射关系;
  • 在多种数据库(SQL Server、Oracle、MySQL 等)上复用同一套业务代码;
  • 支撑快速搭建管理类应用(增删改查、列表查询、主从表编辑)。

EntityDBAccess.cs 文件头注释仍保留着创建时间标记:

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 对象。

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(用户)为例:

代码化 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 读写:

设计意图:

  • 低代码场景下字段可运行时增删,无需重新编译实体类;
  • 主表、从表、流程表单共用同一套读写 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)

类型 含义 典型 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 声明主实体下的从表集合:

  • refKey:从表中外键列(如 FK_Node);
  • DtlEditerModel:批量编辑、查询编辑、自定义 URL 等模式;
  • 流程表单明细(MapDtl)、组织结构子面板均基于此机制。

4.4 一对多 / 多对多映射(AttrOfOneVSM)

Map.AttrsOfOneVSM 描述「一」端实体与「多」端实体通过中间表(MM 表)的关联,例如用户维护所属部门:

支持树形部门、平铺面板等多种维护 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 的优势

  1. 元数据一体 :同一 Attr 描述列类型、控件、外键、枚举、默认值,设计器与运行引擎零阻抗。
  2. 企业功能内建:主从表、多对多维护、列表查询、权限位、关联方法,减少重复劳动。
  3. 低代码原生 :运行期可 GenerMap(),字段可来自数据库配置,适合用户自定义表单。
  4. 久经生产验证:2002 年至今,与 CCFlow 流程场景深度耦合(附件、签名、公文、轨迹等控件类型)。
  5. 多数据库与国产库 :EntityDBAccess 内可见 Oracle、MySQL、PostgreSQL、达梦、人大金仓等分支。

6.3 驰骋 ORM 的局限

  1. 非标准 ORM:无法直接用于普通 .NET 项目的「引包即用」,与 EF Core 生态(LINQ、迁移、工具链)不兼容。
  2. 弱类型 Row:大型团队纯代码开发时,缺少编译期字段检查,重构依赖全局搜索。
  3. 无现代查询抽象 :没有 LINQ / IQueryable,复杂查询依赖 SqlBuilder 字符串,可读性与组合性弱于 EF。
  4. 无变更跟踪 :更新需显式调用 Update() 并指定字段,易遗漏或误更新。
  5. XML 遗产 :GenerMap(xml) 与代码 EnMap 双路径并存,增加维护心智负担。
  6. 测试与 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 低代码动态表单(元数据优先)

  1. 设计器保存字段 → Sys_MapAttr;
  2. MapDtl.GenerMap() / MapData.GenerMap() 合成 Map;
  3. GEDtl / GEEntity 作为通用实体承载数据;
  4. 前端根据 MapAttr JSON 渲染控件。

八、总结

驰骋 ORM 是一段从 2002 年 XML 映射 演进到 2003 年 Map/Attr/Attrs 代码化元数据 、并在二十余年中持续扩展为低代码应用框架底座的技术路线。

它的本质不是「对象---关系映射库」,而是:

用统一的 Map 元数据,同时描述数据库结构、实体行为、UI 控件、主从与多对多关系、外部数据源与查询条件,并驱动 CCFlow、JFlow、组织结构与低代码表单的全生命周期。

与 Entity Framework、Hibernate 等市面 ORM 相比,驰骋 ORM 在标准性、强类型、现代查询 上不占优,但在低代码、流程、表单、组织一体化场景具有不可替代的语义内聚优势。理解这一边界,是正确使用和扩展驰骋 BPM 平台的前提。


相关推荐
zm4351 年前
bpmn.js 自定义绘制流程图节点
前端·bpmn-js
Solon阿杰1 年前
solon-flow基于bpmnJs的流程设计器
javascript·bpmn-js
MiyueFE2 年前
07-源码篇6:Featrues 体验优化与功能扩展(一)
bpmn-js
MiyueFE2 年前
06-源码篇5:CommandStack 命令处理与记录的栈
bpmn-js
MiyueFE2 年前
02-源码篇1:Injector 依赖注入模式的实现
工作流引擎·bpmn-js
得物技术2 年前
探索BPMN—工作流技术的理论与实践|得物技术
javascript·bpmn-js
胖蔡3 年前
聊一聊bpmn-js中的Viewer和Modeler
前端·workflow·bpmn-js
我人有的和3 年前
bpmn-js中定制工具面板
bpmn-js
sye3 年前
基于React的工作流 bpmn.js基本使用(3)-流程模拟的使用(运行过程中暂停、继续,多分路运行)
前端·bpmn-js