【开源】跨语言·跨平台·跨数据库 (2)------ 用宝轻量框架设计必读
从"数组返回太土"说起:六个设计原则,看懂真正的跨语言架构
咏方舟-长江支流 2026-07-13
用宝框架
拥抱第一 · 一次书写 · 三端复用
C# / Java / ArkTS 直接使用,不用重写
------跨平台、跨语言,全球第一套轻量级架构范式
为什么敢说"拥抱第一"?
用宝框架是全球第一套同时做到以下三点的轻量级架构:
一解跨平台: 基于 .NET Standard 2.0,Windows / Linux / macOS 全系列通吃。同时通过跨语言能力,间接覆盖 Java 生态(Windows/Linux/macOS/任何有JVM的平台)和鸿蒙生态(手机/平板/智慧屏/车机)。
二解跨语言: 从设计之初就考虑跨语言。接口、基类、返回结构、命名规范全部语言中立。C# 代码直接复制到 Java 和 ArkTS 中,只改语法符号,不改逻辑结构。不是翻译,是直接复用。
三解是第一: 全球第一套"一次书写,三端复用"的轻量级架构。传统跨语言方案(COM/gRPC/Thrift)需要定义接口、各语言分别实现,本质还是各写各的。用宝框架是真正的"C# 写一次,Java 直接用,ArkTS 直接用",不用翻译器,不用中间层,不用代码生成。
放在"三解是第一"之后、"引子"之前,用引用块或灰色背景强调:
明白了,只转这一句,放在"三解是第一"后面:
你现在用 .NET 写的所有代码------实体、仓储、服务、接口、返回结构------在迁移到 Java 或 ArkTS 时,架构、分层、接口命名、继承关系、泛型约束全部保持不变,只需要按目标语言的语法规则做形式上的转换(比如类型后置、冒号改 implements),而设计意图、业务逻辑、调用链路完全一致。
这个转换是机械的、可自动化的,本质上不需要人手工"翻译",直接用一个工具就可以完成。
这就是用宝框架"跨语言"的真正含义:不是代码的搬运,是设计的迁移。你的业务知识、架构经验、分层思想,可以在 C# / Java / ArkTS 三端之间无缝复用,不需要重新学习、重新设计、重新积累。
这是全球第一套做到这一点的轻量级架构。

用宝框架架构设计原则
------ 从"数组返回太土"说起
引子:一个"太土了"的争议
有一天,有位程序员看了前端调用WebAPI代码:
csharp
/// <summary>
/// 调用ProductDemo.Core项目-->用宝轻量框架(UserBaoTech.Foundation)
/// 前端CRUD调用示例productdemo.html见https://blog.csdn.net/flygoldfish/article/details/162789093
/// 开源文档及下载 www.userbao.com 及 GitHub、https://blog.csdn.net/flygoldfish
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet]
public async Task<MessageResult<Product>> GetList([FromQuery] ProductQuery query)
{
var request = new GetProductListRequest { Query = query };
return await _getListService.ExecuteAsync(request);
}
}
}
然后单步跟进去用宝框架的代码,看到上面代码中的 MessageResult 当它返回分页数据时,Rows 字段的类型就是 T\[\](在这里是 Product\[\])。他说怎么不用 List ,你这也太土了吧?
"你这也太土了吧,返回数据居然用
T[]数组?现在谁不用List<T>啊?"
他没说错,在 C# 里,List<T> 确实更常用、更"现代"。但用宝框架选择 T[],不是因为写代码的人水平不行,而是因为------
用宝框架的代码是要直接复制到 Java 和鸿蒙 ArkTS 里用的。
T[] 在 C#、Java、ArkTS 中都是原生语法,不需要额外引入任何命名空间或类库。如果用了 List<T>,Java 那边得换成 ArrayList<T>,ArkTS 那边得换成 Array<T>,代码就"变形"了。
所以,不是用宝框架太土,是提问的人没看懂它要跨什么。
这就是用宝框架的第一个设计原则:跨语言一致性。
原则一:跨语言一致性
用宝框架的定位,不是"又一个 C# 框架",而是一套可以在 C#、Java、ArkTS 之间直接复制的架构范式。
具体做法:
- 接口命名统一:
IEntity<TKey>、IRepository<T>、IService<TParam, TResult> - 基类命名统一:
RepositoryBase<T>、ServiceBase<TParam, TResult>、KeyServiceBase<TResult> - 返回结构统一:
MessageResult<T> - 数据类型统一:
T[]而不是List<T>,Dictionary<string, object>而不是IDictionary的特定实现
效果 :用宝框架的 C# 代码,可以直接复制到 ArkTS 项目中,稍作语法调整即可运行;复制到 Java 项目中,只需要把特性([Table])换成注解(@Table),底层逻辑完全一致。
这不是"翻译",是"直接复用"。
原则二:轻量化
用宝框架不追新、不堆砌、不依赖。
核心类库 UserBaoTech.Foundation 的目标框架是 .NET Standard 2.0,唯一的 NuGet 引用是 System.Text.Json。没有 EF、没有 Dapper、没有 AutoMapper,没有任何重量级依赖。
为什么这么轻?
.NET Standard 2.0是 .NET 世界里最后一份"通用契约",.NET Framework 4.8、.NET Core 3.1、.NET 6/8/10 都能引用它,永不淘汰- 不用 ORM,手写 SQL,因为 SQL 本身是跨语言标准,在 Java 和 ArkTS 里也能执行
- 唯一的 JSON 库是为了序列化,JSON 也是跨语言标准
轻量,是为了让用宝框架不绑定任何特定平台或版本。你现在写的代码,十年后还能用。
原则三:开放式 / 可插拔设计
用宝框架的核心组件全部基于接口,而不是具体实现。
| 组件 | 接口 | 默认实现 | 可替换为 |
|---|---|---|---|
| 日志 | ILogger |
NullLogger |
Serilog、NLog、自定义 |
| 缓存 | ICacheProvider |
NullCacheProvider |
Redis、MemoryCache、自定义 |
| 配置 | IConfigProvider |
MemoryConfigProvider |
JSON文件、数据库、环境变量 |
| 数据访问 | IDataAccessExecutor |
DataAccessExecutor |
自定义执行器 |
效果 :你不想用日志,就保持 NullLogger;你想用 Redis,就实现 ICacheProvider 并注册;你想换数据库,就改连接字符串,业务代码一行不改。
这是"插拔式"设计,不是"写死式"设计。
原则四:单一职责原则
用宝框架的业务服务层,每个 Service 只做一件事。
| Service | 职责 |
|---|---|
GetProductListService |
只负责查询列表 |
CreateProductService |
只负责新增 |
UpdateProductService |
只负责更新 |
GetProductByIdKeyService |
只负责按主键查询 |
DeleteProductKeyService |
只负责删除 |
为什么拆这么细?
- 改了查询逻辑,不会影响到删除逻辑
- 新增一个过滤条件,只需要改对应的查询 Service
- 每个 Service 的代码量很小,容易理解、容易测试
- 团队协作时,两个人不会改到同一个文件
这是"拆开以隔离变化",不是"为了拆而拆"。
原则五:实体与查询参数分离
用宝框架把"数据库表结构"和"前端查询条件"分成了两个不同的概念。
- 实体(Entity):对应数据库表,包含所有字段(包括内部字段、敏感字段)
- 查询参数(Query):对应前端传入的过滤条件,只包含前端关心的字段
csharp
// 实体:对应数据库表,包含所有字段
public class Product : IEntity<string>
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public string InternalCode { get; set; } // 内部字段,不对外
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsDeleted { get; set; }
}
// 查询参数:前端传入的过滤条件
public class ProductQuery : QueryParametersBase
{
public string Name { get; set; } // 前端只关心名称搜索
public decimal? MinPrice { get; set; } // 前端只关心价格范围
public decimal? MaxPrice { get; set; }
}
好处:
- 数据库表加了新字段,不影响查询参数
- 查询参数加了新条件,不影响实体
- 前端只传它需要的字段,不关心数据库内部结构
原则六:返回模型与实体分离
用宝框架不直接把实体返回给前端,而是通过 MessageResult<T> 和 DTO 进行隔离。
为什么?
- 数据库里有密码字段,返回时屏蔽掉
- 数据库里有内部状态码(如
0、1、2),返回时翻译成前端能看懂的中文 - 数据库表有 20 个字段,前端只关心 3 个,只返回那 3 个
csharp
// 实体:存了密码
public class User : IEntity<string>
{
public string Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; } // 不能返回给前端
public int RoleCode { get; set; } // 内部状态码
}
// 返回模型:只返回前端需要的字段
public class UserDto
{
public string Id { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; } // 翻译后的中文
}
好处:
- 不暴露内部存储细节
- 前端只收到它需要的数据
- 实体变化时,只要返回模型不变,前端不受影响
原则七:AI可扩展性
用宝轻量框架不只是为"人"设计的,也是为"AI"设计的。
可扩展性不只体现在接口的可插拔、组件的可替换,还体现在配置的标准化和模板的通用化。SQL 是标准查询语言,JSON 是标准数据格式,XML 是标准结构描述,HTML 是标准展示模板------而这四种标准,恰好都是 AI 天然擅长生成和处理的内容。
金质打印通是作者的开源作品之一,而它的姊妹篇"天下报表"早在 2006年就采用 XML 做报表配置。如今这套报表引擎已整合进用宝框架,成为其上层能力的重要验证案例。
用宝框架的设计,本质上是在为 AI 参与软件工程提供一套清晰的边界和规范。框架负责定义"怎么写",AI 负责生成"写什么"。开发者只需要描述业务需求,AI 就能按框架规范生成符合标准的代码、配置和模板。
这套轻量级架构,非常契合中小企业快速开发、灵活迭代、AI 辅助编程的实际需求。
你说得对,我之前的表述确实不准确。用宝框架本身就是多层架构,天然支持微服务。
productdemo.html 通过 WebAPI 调用后端,后端 Controller → Service → Repository 分层清晰,每个服务独立部署、独立运行,本身就是微服务架构的一种实现形态。
所以原则八的表述应该修正为:
原则八:局限性
用宝框架的定位是轻量、标准、可迁移 ,主战场是中小企业标准化业务系统。
用宝框架本身就是多层架构,天然支持微服务------Controller 层、Service 层、Repository 层边界清晰,每个服务可以独立拆分、独立部署。productdemo.html示例 通过 WebAPI 调用后端的模式,本身就是一种微服务实现。
用宝框架的局限,主要体现在高并发、集群、分布式事务这类大规模场景------这些场景对连接池、缓存、消息队列、分布式一致性有更高的要求,而这些不是用宝框架当前要解决的问题,需要由上层扩展来补充。
但架构本身支持微服务,这一点没有问题。
用宝框架只做一件事------把"中小企业的常规业务开发"这件事,做得足够轻、足够稳、足够可迁移,同时为微服务提供清晰的分层基础。
机关枪可以打很多仗,但如果你非要拿它去打航母,那是你选错了武器。用宝框架清楚自己的边界------它解决的是中小企业 80% 的常规业务场景,剩下的 20%,留给真正需要它的场景去扩展。
期待中的AI自然人报表
金质打印通从2003年开发并开源,以及它的姊妹篇 天下报表 应用 XML 配置报表,现在开始移植到用宝框架,将用宝框架开源、天下报表也将开源。金质打印通当初说的是**"金质打印通,通打开下报表",那天下报表,让天下人直接口述出报表**。因为AI最擅长结构化语言,更适用于自然人直接描述想要的报表。因为AI最擅长XML、SQL、Html这些最通用的语言。
以下是部分设计QA
亮点一:MessageResult 的泛型设计
csharp
public class MessageResult<T> : MessageResult
{
public T Data { get; set; } // 非分页数据
public int Total { get; set; } // 分页总记录数
public T[] Rows { get; set; } // 分页数据列表
}
设计要点:
- 分页与非分页不混用:分页只填
Rows+Total,非分页只填Data - 泛型让返回类型灵活:
MessageResult<Product>返回产品,MessageResult<string>返回新增 ID - 前端只认五个字段:
success、message、data、rows、total,所有接口都一样
为什么不用 List<T>? 前面已经说过了------为了跨语言一致性。
亮点二:IEntity 的泛型设计
csharp
public interface IEntity<TKey>
{
TKey Id { get; set; }
DateTime CreatedAt { get; set; }
DateTime UpdatedAt { get; set; }
bool IsDeleted { get; set; }
}
为什么用泛型?
- 大部分表用
string(GUID),但旧系统可能需要int(自增ID) - 泛型让主键类型灵活,不绑死任何一种
CreatedAt、UpdatedAt、IsDeleted是所有实体都有的公共字段,抽到接口里不重复定义
亮点三:KeyRequest 消灭重复 DTO
csharp
public class KeyRequest
{
public string Id { get; set; }
}
问题:每个实体都有按主键查询和按主键删除的需求,如果每个都定义一个 DTO:
csharp
public class GetProductByIdRequest { public string Id { get; set; } }
public class DeleteProductByIdRequest { public string Id { get; set; } }
public class GetUserByIdRequest { public string Id { get; set; } }
// ... 无穷无尽
用宝框架的解决方案 :直接用 KeyRequest,配合 KeyServiceBase:
csharp
public class GetProductByIdKeyService : KeyServiceBase<MessageResult<Product>>
{
protected override async Task<MessageResult<Product>> DoExecuteAsync(string id)
{
// 业务逻辑
}
}
效果 :不需要为每个实体定义"按主键查询"和"按主键删除"的 DTO,一个 KeyRequest 通吃。
亮点四:数据库提供者(方言适配)
用宝框架的 DataAccessExecutor 内部,通过 DatabaseType 枚举统一处理不同数据库的差异:
- 参数前缀 :SQL Server/MySQL 用
@,Oracle 用:,ODBC 用? - 分页 SQL :SQL Server 用
OFFSET FETCH,MySQL 用LIMIT,Oracle 用ROWNUM - 自增 ID 回读 :SQL Server 用
SCOPE_IDENTITY(),MySQL 用LAST_INSERT_ID() - 日期格式化 :Oracle 需
TO_DATE,其他数据库直接用字符串
当前采用 switch 集中处理主流数据库方言,后续准备改为提供者(Provider)模式,支持用户自定义扩展,同时保留默认实现,确保开箱即用。
效果:
- 业务层写的是标准 SQL,完全感知不到数据库差异
- 切换数据库时,只改连接字符串,业务代码一行不改
- 新增数据库支持时,只需在
DataAccessExecutor中增加分支
这个设计不是"写死",是"集中处理"。所有方言逻辑在一个地方,上层干干净净。
亮点五:依赖注入传连接,不自己建连接
csharp
// Program.cs
var connectionString = builder.Configuration.GetConnectionString("Default");
builder.Services.AddScoped<IDbConnection>(sp => new MySqlConnection(connectionString));
builder.Services.AddScoped<IDataAccessExecutor>(sp =>
{
var conn = sp.GetRequiredService<IDbConnection>();
var logger = sp.GetRequiredService<ILogger>();
return new DataAccessExecutor(conn, DatabaseType.MySql, logger);
});
为什么这样设计?
- 用宝框架不负责连接的生命周期,连接由上层应用管理
- 上层可能已经建好了连接,或者需要复用已有连接
- 用宝框架只要求传入一个
IDbConnection,不关心它怎么来的 - 这样框架能适配任何已经存在的系统,而不是要求别人按你的方式建连接
这是"依赖注入"的正解------框架不替你做决定,只负责执行。
结语
用宝框架不是一个"炫技"的框架。
它没有用最新的 .NET 语法,没有用最酷的设计模式,没有引入任何时髦的第三方库。它做的唯一一件事,就是把 20 年经验中沉淀下来的、最稳定的设计原则,固化成了一套可复用的代码结构。
它的设计原则可以总结为:
- 跨语言一致性:C# 写的代码,可以直接在 Java 和 ArkTS 里用
- 轻量化:零依赖,永不淘汰
- 开放式设计:所有组件可插拔、可替换
- 单一职责:每个类只做一件事
- 实体与查询分离:前端参数与数据库表互不干扰
- 返回模型与实体分离:不暴露内部存储细节
所以,下次有人看到用宝框架返回 T[] 说"太土了",你可以告诉他:
"不是太土了,是你看不懂它的设计意图。"
用宝框架的设计,不是为了迎合某一种语言的"时尚",而是为了跨越语言、跨越平台、跨越时间。
这才是它真正有价值的地方。
版本 :第一版
发布日期 :2026年7月13日
作者 :周方勇/咏方舟-长江支流(金质打印通、用宝框架开源作者)
适用范围:所有基于 UserBaoTech.Foundation 的新项目开发
反馈与交流 :欢迎在评论区留言或私信交流。如果你正在寻找一套 不用 EF、不用 Dapper、可跨语言迁移 的轻量级基架,用宝框架或许是一个值得尝试的选择。
用宝框架,拥抱第一 · 一次书写 · 三端复用------不仅是用户的朋友,而且是用户的宝贝