XYS.FR
4.2.0
dotnet add package XYS.FR --version 4.2.0
NuGet\Install-Package XYS.FR -Version 4.2.0
<PackageReference Include="XYS.FR" Version="4.2.0" />
<PackageVersion Include="XYS.FR" Version="4.2.0" />
<PackageReference Include="XYS.FR" />
paket add XYS.FR --version 4.2.0
#r "nuget: XYS.FR, 4.2.0"
#:package XYS.FR@4.2.0
#addin nuget:?package=XYS.FR&version=4.2.0
#tool nuget:?package=XYS.FR&version=4.2.0
XYS.FR.V3
FastReport 数据契约与配置层。只负责:把 POCO 实体注册为 DataSet 表结构、生成 FastReport Designer 所需的 FRTables.frd、加载模板/行列对照配置。不含任何渲染引擎(PDF/图片/打印由 XYS.FRNet / XYS.FRCore 等宿主项目实现,它们继承本项目的 BaseFRService)。
- 目标框架:
net8.0/net462/netcoreapp3.1/netstandard2.0/netstandard2.1 - 依赖:
XYS.Utils.Sys、XYS.Utils.Log4Net(同解决方案项目引用) - 程序集名:
XYS.FR(命名空间同名)
核心概念
| 类型 | 职责 |
|---|---|
IFREntity / FRBaseEntity |
实体标记接口 / 抽象基类;子类用 [FRExport] 标注要导出的属性 |
FRExportAttribute |
属性级导出标记,可选 Name 覆盖列名、IsExport=false 显式排除 |
DataStruct |
静态注册中心:Register(Type) 幂等注册 → GetSet() 拿到空 DataSet 副本 → EnsureXmlFile() 落盘 Designer 用的 frd |
ExportData.ExportEntity |
把实体填充到 DataSet 里的 DataRow |
FRReport |
一次打印/导出的总载荷:模板号 + 实体列表 + 份数/副本名 |
IConfigManager / XmlConfigManager |
模板号 → 模板文件名、行 ID → 列名对照 |
IFRExportService |
导出契约:ExportStream。所有渲染宿主都实现 |
IFRPrintService |
打印契约:Print。仅由具备打印能力的引擎实现,与导出刻意分开 |
BaseFRService |
服务基类,实现 IFRExportService;模板方法:GetFRTypes + InitFRConfig;调用 Init() 一次完成注册 + 配置 |
快速使用
1. 定义实体
using XYS.FR;
public class LabItem : FRBaseEntity
{
[FRExport] public string ItemName { get; set; }
[FRExport] public string Value { get; set; }
[FRExport] public string Unit { get; set; }
[FRExport(false)] public string InternalNote { get; set; } // 不导出
[FRExport(true, "结果标志")] public string Flag { get; set; } // 列名重命名
}
2. 继承 BaseFRService
public sealed class LabFRService : BaseFRService
{
public LabFRService()
{
LoggerName = "LabFRService";
ActualType = typeof(LabFRService);
Init(); // 注册实体 + 加载配置
}
protected override List<Type> GetFRTypes() => new List<Type>
{
typeof(FRInfo),
typeof(LabItem),
};
protected override void InitFRConfig()
{
var cfg = new XmlConfigManager(); // 默认 AppBase/Config/fr.config.xml
cfg.Init(); // 失败会抛异常,视为致命错误
ConfigManager = cfg;
}
public override void ExportStream(FRReport report, Stream stream, ExportType type = ExportType.Pdf)
{
// 由具体渲染引擎实现(见 XYS.FRNet / XYS.FRCore)
}
}
3. 生产 FRReport 并渲染
var report = new FRReport { ModelNo = "LAB001", RID = "R123" };
report.Items.Add(new FRInfo { ID = "1", RID = "R123", PatName = "张三" });
report.Items.Add(new LabItem { ID = "1", RID = "R123", ItemName = "WBC", Value = "6.5" });
using (var fs = File.Create("out.pdf"))
{
labSvc.ExportStream(report, fs, ExportType.Pdf);
}
4. 配置文件 Config/fr.config.xml 示例
<?xml version="1.0" encoding="utf-8"?>
<frConfig>
<modelMap>
<model modelNo="LAB001" name="常规生化" alias="lab" fileName="lab_normal.frx" />
<model modelNo="LAB002" name="血常规" alias="cbc" fileName="lab_cbc.frx" />
</modelMap>
<mapMap>
<map tType="0" rowId="WBC" colName="C0" />
<map tType="0" rowId="RBC" colName="C1" />
</mapMap>
</frConfig>
关键契约
| 成员 | 语义 |
|---|---|
DataStruct.Register(Type) |
幂等注册;非 IFREntity 或已注册类型静默跳过 |
DataStruct.Register(IEnumerable<Type>) |
批量注册 |
DataStruct.GetSet() |
返回空数据集副本(含全部注册表结构) |
DataStruct.EnsureXmlFile() |
磁盘无 FR/FRTables.frd 时按当前注册生成;已存在不覆盖 |
DataStruct.Types |
已注册类型只读快照 |
ExportData.ExportEntity(entity, ds) |
单实体填充;表未注册抛 InvalidOperationException |
BaseFRService.Init() |
注册 + 生成 frd + 加载配置;同一进程多子类并存安全 |
BaseFRService 的打印 |
不提供。需要打印的宿主自行声明实现 IFRPrintService |
BaseFRService.OpenModel(modelNo) |
打开模板内容流,转发到 ConfigManager.OpenModel;渲染宿主只认这个口子 |
BaseFRService.ModelRoot |
转发属性,实际存放于 BaseConfigManager.ModelRoot |
IFRExportService.ExportStreamAsync |
默认实现 = 线程池 + 丢弃结果式取消;宿主可重写接引擎的真取消 |
IConfigManager.OpenModel(modelNo) |
调用方负责释放返回的流;默认实现读本地文件 |
BaseConfigManager.ModelRoot |
模板根目录,未设置时惰性取 AppBase/FR |
XmlConfigManager(string) |
可通过构造函数注入配置文件路径 |
IConfigManager.Maps / Models |
IReadOnlyList,实现类内部维护 |
4.2.0 变更(相对 4.1.0)
破坏性(两个接口都加了成员,外部若有直接实现者需同步改;netstandard2.0 不支持默认接口方法,无法用 DIM 兜底)
IConfigManager新增Stream OpenModel(string modelNo)IFRExportService新增Task ExportStreamAsync(FRReport, Stream, ExportType, CancellationToken)
非破坏性
ModelRoot从BaseFRService下沉到BaseConfigManager;BaseFRService.ModelRoot保留为转发属性,既有子类的ModelRoot = ...写法照旧有效(含"在Init()之前赋值"这种推荐写法:值会先暂存,由Init()补写给配置管理器)BaseFRService新增protected virtual Stream OpenModel(string modelNo)BaseConfigManager新增GetModelFullName与OpenModel默认实现(读本地文件,FileShare.Read共享打开,因为服务端会并发渲染同一份模板)
模板可以不在文件系统上了
改动前渲染宿主只能拿到一个磁盘路径(GetModelFullName);改动后统一走 OpenModel 取内容流。
这样模板才可能入对象存储并带版本号——服务端渲报告、客户端渲标签、设计器改模板,三方拿到同一份且可回滚。
模板留在各机文件系统上,必然每台一份副本、改了不同步、改坏了退不回去。
宿主要从 MinIO 取模板,只需自己实现 IConfigManager(或继承 BaseConfigManager)并重写 OpenModel,
渲染宿主一行不改:
public sealed class MinioConfigManager : BaseConfigManager
{
public override void Init() { /* 加载 models / maps */ }
public override Stream OpenModel(string modelNo)
{
//GetModelName 未匹配时返回 default.frx,与本地实现语义一致
return _minio.GetObject(_bucket, GetModelName(modelNo)); //调用方负责释放
}
}
GetModelFullName 仍然保留且行为不变,但渲染宿主已不再使用它——日志里的模板标识也随之从
"模板全路径"改成"模板编号(文件名)",因为模板可能根本没有路径。
为什么要异步 + 取消
报告预览界面是"左侧列表选行 → 右侧显示渲染后的 PDF"。用户按方向键快速划过 20 行, 会产生 20 个渲染请求,其中 19 个在返回时已经没人要了。更要紧的不是浪费,是乱序响应: 第 3 行的响应晚于第 7 行返回时,右侧显示的是第 3 行的报告,而左侧高亮在第 7 行—— 审核医生照着右边看,可能给别人的报告点通过。这是临床安全缺陷,一个人手快就能触发。
取消不是唯一的防线(调用方还要做响应认领和防抖),但同步接口下连取消这一层都没有。
BaseFRService 的默认实现是 Task.Run(..., ct):ct 能挡住尚未开始的渲染,
已经进入引擎的那一份仍会跑完、结果被丢弃。渲染引擎若提供可中断的准备阶段,宿主应重写接上去——
XYS.FRCore 在 net462 / net8.0 上就是这么做的(Report.PrepareAsync(ct))。
注:本次同样是破坏性变更而只升次版本号,理由与 4.1.0 一致(三个项目间均为
ProjectReference)。
4.1.0 变更(相对 4.0.0)
破坏性
IFRService拆分为IFRExportService(ExportStream)与IFRPrintService(Print),原IFRService已删除BaseFRService不再声明Print抽象成员,只实现IFRExportService
为什么
原来 IFRService 把导出与打印绑在一起,导致每个渲染宿主都被迫实现两者。但 FastReport.Core 是跨平台版,不含任何打印 API(Report.Print / Report.PrintPrepared / PrintSettings 在 2023.3.13 中均不存在),XYS.FRCore 只能写出一个恒抛 NotSupportedException 的 Print——典型的 Liskov 违例。拆分后:
| 宿主 | 实现 |
|---|---|
XYS.FRNet(FastReport .NET 完整版) |
BaseFRService + IFRPrintService |
XYS.FRCore(FastReport.Core 跨平台版) |
仅 BaseFRService,不实现打印 |
XYS.FRCore 中那个恒抛异常的 Print 已随之整块删除。
注:本次为破坏性变更,按 semver 本应升主版本。因 4.0.0 当天才提交且从未对外发布(三个项目均为
ProjectReference),经决策只升次版本号 4.1.0,特此记录。
4.0.0 变更(相对 3.1.0)
破坏性
ExportType枚举值改 PascalCase:pdf/jpg/html/xls→Pdf/Jpg/Html/Xls- XML DTO 类重命名:
FRModel_Xml→FRConfigXml、FRModel_M→FRModelXml、FRMap_M→FRMapXml(XML 节点名不变,兼容旧配置文件) IConfigManager.Maps/Models由List<T>收紧为IReadOnlyList<T>;外部不再可整体替换IFREntity接口去掉误导性的[FRExport]装饰(C# 属性 Attribute 不从接口继承,原来就没生效)- 删除死接口
IFRModel、空壳ISetting/BaseSettings FRInfo拼写修正:Equipentment→Equipment、SmapleExp→SampleExp、ColletTime→CollectTime、InceptTime→AcceptTime、InceptSign→AcceptSignDataStruct从"通过公共TList.Add修改集合"改为"通过Register()幂等注册";InitXMLStruct/InitRamStruct已由Register + EnsureXmlFile取代BaseConfigManager.Maps/Models公开的可写属性已改为只读;子类改用 protected 字段_maps/_models
修复
_initFlag是静态导致多子类竞态:现改为HashSet<Type>per-type 幂等注册,多个BaseFRService子类可安全共存[FRExport(false)]曾被忽略:DataStruct.IsExport(PropertyInfo)现真正读取IsExport属性FRExportAttribute.Name曾被忽略:现作为导出列名生效XmlConfigManager.Init静默吞异常:文件不存在 / 解析失败 / 反序列化 null 都会抛异常向上传播Models/Maps节点为 null 时 NRE:分别判空throw ex;丢失堆栈:移除无意义 try/catchExportData.IsExport与DataStruct.IsExport重复代码:统一到DataStructExportData抛通用Exception:改为InvalidOperationException/ArgumentNullException
清理
- 依赖由
PackageReference XYS.Utils.Sys 2.2.1.1 / XYS.Utils.Log4Net 2.1.1.1改为ProjectReference,与解决方案统一 DataStruct.Gender*AttrDic→Build*Attrs(原方法名拼写疑似 Generate 之误)static readonly string常量 →const- 删除
DataStruct.ConvertXml2DataTable/ConvertXml2TableRelation/GetRelationColumns等死代码 MethodBase.GetCurrentMethod().Name反射调用 →nameof
建议
Init()在服务构造末尾调用,WorkerCount等属性设置在前XmlConfigManager配置加载失败视为致命错误,让 App 启动失败胜过运行期打空模板[FRExport(name: "...")]用于列名重命名,Designer 端字段名与 C# 属性名可解耦- 同进程内允许多个
BaseFRService子类共存(如 Lab / Exam / EMR),共享同一DataStruct实体注册表
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 is compatible. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETCoreApp 3.1
- XYS.Utils.Log4Net (>= 2.5.0)
- XYS.Utils.Sys (>= 2.6.0)
-
.NETFramework 4.6.2
- XYS.Utils.Log4Net (>= 2.5.0)
- XYS.Utils.Sys (>= 2.6.0)
-
.NETStandard 2.0
- XYS.Utils.Log4Net (>= 2.5.0)
- XYS.Utils.Sys (>= 2.6.0)
-
.NETStandard 2.1
- XYS.Utils.Log4Net (>= 2.5.0)
- XYS.Utils.Sys (>= 2.6.0)
-
net8.0
- XYS.Utils.Log4Net (>= 2.5.0)
- XYS.Utils.Sys (>= 2.6.0)
NuGet packages (10)
Showing the top 5 NuGet packages that depend on XYS.FR:
| Package | Downloads |
|---|---|
|
XYS.FR.Net462
Description |
|
|
XYS.FR.ZhiFang
Package Description |
|
|
XYS.FR.NetCore
Package Description |
|
|
XYS.FRCore
FastReport.Core 渲染宿主:实现 XYS.FR 的 BaseFRService 导出契约,提供 PDF/JPG/HTML/XLSX 导出。不支持打印(Core 版无打印 API)。 |
|
|
XYS.FR.NetFr4
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.2.0 | 200 | 9/5/2026 |
| 4.0.0 | 114 | 8/17/2026 |
| 3.0.2.7 | 94 | 9/4/2026 |
| 3.0.2.6 | 340 | 4/18/2024 |
| 3.0.2.5 | 299 | 3/13/2024 |
| 3.0.2.4 | 282 | 1/16/2024 |
| 3.0.2.3 | 268 | 1/16/2024 |
| 3.0.2.2 | 268 | 1/16/2024 |
| 3.0.2.1 | 265 | 12/4/2023 |
| 3.0.1.11 | 267 | 11/26/2023 |
| 3.0.1.10 | 240 | 11/26/2023 |
| 3.0.1.9 | 260 | 11/25/2023 |
| 3.0.1.8 | 235 | 11/25/2023 |
| 3.0.1.7 | 247 | 11/24/2023 |
| 3.0.1.6 | 266 | 10/27/2023 |
| 3.0.1.5 | 239 | 10/27/2023 |
| 3.0.1.4 | 239 | 10/26/2023 |
| 3.0.1.3 | 1,016 | 9/14/2022 |
| 3.0.1.2 | 893 | 9/13/2022 |
| 2.0.3.3 | 836 | 9/14/2022 |