如何利用FhirDotNet获取同类型FHIR资源差异属性列表并实现增量同步
针对你在使用FHIR .NET API同步患者资源时遇到的增量更新痛点——既要同步源存储库的变更,又要保留FHIR服务器中独有的数据(比如患者通过移动端添加的信息),我来分享下官方提供的工具和可行的实现方案:
一、FHIR .NET API内置的差异检测工具
其实FHIR .NET API(Core包)已经提供了成熟的资源对比功能,核心是DeepComparer类(位于Hl7.Fhir.Comparison命名空间下),它能深度对比两个同类型FHIR资源的所有属性,生成结构化的差异列表,而且完全遵循FHIR的资源结构规范(比如处理重复元素、嵌套资源、可选字段等)。
使用示例:
using Hl7.Fhir.Model; using Hl7.Fhir.Comparison; // 假设sourcePatient是源存储库的患者实例,targetPatient是FHIR服务器的患者实例 var comparer = new DeepComparer(); var differenceList = comparer.Compare(sourcePatient, targetPatient); // 遍历差异结果,查看具体变更 foreach (var diff in differenceList) { // diff.Path:FHIR路径格式的属性位置,比如"Patient.name[0].given[0]" // diff.SourceValue:源资源的属性值 // diff.TargetValue:目标资源的属性值 Console.WriteLine($"变更路径:{diff.Path} | 源值:{diff.SourceValue} | 当前FHIR值:{diff.TargetValue}"); }
这个工具会自动跳过源中为空的属性(只对比有值的字段),完全符合你“仅同步源中非空变更”的需求,而且不需要自己写复杂的反射逻辑。
二、基于差异结果实现增量更新
拿到差异列表后,你可以借助PocoNavigator(位于Hl7.Fhir.ElementModel命名空间)来精准更新FHIR目标资源的对应属性,这个类能通过FHIR路径直接定位到资源的具体字段,避免手动反射的繁琐和错误。
增量更新实现示例:
using Hl7.Fhir.ElementModel; using Hl7.Fhir.Serialization; public static void ApplyIncrementalPatientUpdate(Patient targetPatient, Patient sourcePatient) { var comparer = new DeepComparer(); var differences = comparer.Compare(sourcePatient, targetPatient); // 创建目标资源的导航器,用于定位和修改属性 var targetNavigator = new PocoNavigator(targetPatient); foreach (var diff in differences) { // 仅处理源资源中有值的变更(避免覆盖FHIR中独有的数据) if (diff.SourceValue != null) { // 导航到目标资源的对应属性路径 if (targetNavigator.MoveToPath(diff.Path)) { // 获取源资源的对应属性值 var sourceNavigator = new PocoNavigator(sourcePatient); sourceNavigator.MoveToPath(diff.Path); var updatedValue = sourceNavigator.Current.Value; // 更新目标资源的属性 targetNavigator.Current.Value = updatedValue; } } } }
调用这个方法后,targetPatient就只会被源资源中非空且有变更的属性更新,FHIR中原有的独数据会被完整保留。
三、若需自定义实现的替代方案(伪代码逻辑)
如果因为API版本限制无法使用内置工具,你可以基于反射实现深度对比,但要注意FHIR资源的复杂结构(比如重复元素、嵌套资源)。核心逻辑如下:
- 遍历资源的所有可读写属性,排除FHIR内部的私有属性(以
_开头的字段) - 对简单类型:仅当源值非空且与目标值不同时,标记为变更
- 对嵌套资源:递归调用对比方法
- 对列表类型:对比列表长度和每个元素的内容
伪代码实现:
using System.Reflection; using System.Collections; public static List<string> GetChangedPatientProperties(Patient source, Patient target) { var changedProps = new List<string>(); // 获取Patient类的所有可读写公共属性 var properties = typeof(Patient).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead && p.CanWrite && !p.Name.StartsWith("_")); foreach (var prop in properties) { var sourceVal = prop.GetValue(source); var targetVal = prop.GetValue(target); // 处理嵌套资源(比如ContactPoint、Address等) if (sourceVal is Resource nestedSource && targetVal is Resource nestedTarget) { var nestedChanges = GetChangedProperties(nestedSource, nestedTarget); changedProps.AddRange(nestedChanges.Select(c => $"{prop.Name}.{c}")); } // 处理列表类型(比如多个name、telecom) else if (sourceVal is IEnumerable sourceList && targetVal is IEnumerable targetList) { var sourceItems = sourceList.Cast<object>().ToList(); var targetItems = targetList.Cast<object>().ToList(); if (sourceItems.Count != targetItems.Count) { changedProps.Add(prop.Name); } else { for (int i = 0; i < sourceItems.Count; i++) { if (!Equals(sourceItems[i], targetItems[i])) { changedProps.Add($"{prop.Name}[{i}]"); break; } } } } // 处理简单类型,仅源值非空且不同时标记变更 else if (sourceVal != null && !Equals(sourceVal, targetVal)) { changedProps.Add(prop.Name); } } return changedProps; } // 后续可以根据变更的属性路径,用反射或PocoNavigator完成赋值
不过还是强烈建议优先使用官方的DeepComparer和PocoNavigator,它们经过了充分的测试,能正确处理FHIR资源的各种边缘情况,比如扩展属性、元素的ID和版本信息等。
内容的提问来源于stack exchange,提问作者Geekn
相关产品推荐
相关产品推荐

