迁移ColdFusion至.NET:如何简洁保存含多嵌套类的大型对象?
我之前重构遗留系统时也碰到过几乎一模一样的场景——几百个可选字段拆成一堆嵌套子类,事务里挨个写保存逻辑,代码臃肿到后期改起来心惊胆战。结合你提到的建造者/工厂思路,我推荐用工厂模式+策略模式的组合来重构,完美解决这类重复逻辑问题,而且扩展性拉满。
核心思路
把每个子类的保存逻辑封装成独立的「策略」,用工厂根据实体类型匹配对应的策略,最后在事务里统一执行所有策略的保存操作。这样主逻辑里再也不用写15+个if/switch,新增子类只需要加一个策略类就行。
步骤1:定义统一的保存策略接口
先给所有子实体的保存逻辑定个标准:
public interface ISubEntitySaver<T> { Task SaveAsync(T entity, YourDbContext context, CancellationToken cancellationToken = default); }
这个接口负责封装单个子类的保存逻辑——不管是新增、更新还是关联操作,都放在对应的实现类里。
步骤2:为每个子类实现保存策略
比如你的CustomerInfo子类,就写一个专属的策略类:
public class CustomerInfoSaver : ISubEntitySaver<CustomerInfo> { public async Task SaveAsync(CustomerInfo entity, YourDbContext context, CancellationToken cancellationToken) { // 这里放原来针对CustomerInfo的保存逻辑:判断是否存在、新增/更新、关联处理等 if (entity.Id == 0) { context.CustomerInfos.Add(entity); } else { context.CustomerInfos.Update(entity); } await context.SaveChangesAsync(cancellationToken); } }
把原来散在主方法里的15+段逻辑,逐个搬到对应的策略类里,每个类只负责自己的实体,符合单一职责原则。
步骤3:实现策略工厂
接下来写个工厂,用来根据实体类型获取对应的策略实例。如果用的是ASP.NET Core,可以直接借助DI容器来管理所有策略:
public class SubEntitySaverFactory { private readonly IServiceProvider _serviceProvider; public SubEntitySaverFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public ISubEntitySaver<T> GetSaver<T>() { return _serviceProvider.GetRequiredService<ISubEntitySaver<T>>(); } }
记得在Program.cs里注册所有策略:
builder.Services.AddScoped<ISubEntitySaver<CustomerInfo>, CustomerInfoSaver>(); builder.Services.AddScoped<ISubEntitySaver<OrderDetails>, OrderDetailsSaver>(); // 其他13+个子类的策略注册... builder.Services.AddScoped<SubEntitySaverFactory>();
步骤4:重构主保存逻辑
现在主方法里的代码会变得异常简洁,只需要遍历领域对象的所有子实体,用工厂拿到策略后统一执行:
public async Task SaveLargeDomainObjectAsync(LargeDomainObject domainObject, YourDbContext context, SubEntitySaverFactory saverFactory, CancellationToken cancellationToken) { using var transaction = await context.Database.BeginTransactionAsync(cancellationToken); try { // 遍历所有需要保存的子实体 if (domainObject.CustomerInfo != null) { var saver = saverFactory.GetSaver<CustomerInfo>(); await saver.SaveAsync(domainObject.CustomerInfo, context, cancellationToken); } if (domainObject.OrderDetails != null) { var saver = saverFactory.GetSaver<OrderDetails>(); await saver.SaveAsync(domainObject.OrderDetails, context, cancellationToken); } // 其他子实体的处理... await transaction.CommitAsync(cancellationToken); } catch (Exception ex) { await transaction.RollbackAsync(cancellationToken); // 异常处理逻辑 throw; } }
进阶优化:减少手动遍历代码
如果子实体太多,手动写if判断还是有点麻烦,可以用反射+属性标记来自动发现需要保存的子实体:
- 自定义一个
SaveableSubEntityAttribute,标记在领域对象的子实体属性上; - 用反射遍历领域对象的所有属性,找到带该标记的属性;
- 动态调用工厂获取对应的策略并执行保存。
示例代码大概是这样:
// 自定义属性 [AttributeUsage(AttributeTargets.Property)] public class SaveableSubEntityAttribute : Attribute { } // 在领域对象里标记属性 public class LargeDomainObject { [SaveableSubEntity] public CustomerInfo? CustomerInfo { get; set; } [SaveableSubEntity] public OrderDetails? OrderDetails { get; set; } // ...其他属性 } // 主方法里用反射自动处理 public async Task SaveLargeDomainObjectAsync(LargeDomainObject domainObject, YourDbContext context, SubEntitySaverFactory saverFactory, CancellationToken cancellationToken) { using var transaction = await context.Database.BeginTransactionAsync(cancellationToken); try { var saveableProperties = typeof(LargeDomainObject) .GetProperties() .Where(p => p.GetCustomAttribute<SaveableSubEntityAttribute>() != null && p.GetValue(domainObject) != null); foreach (var prop in saveableProperties) { var entityType = prop.PropertyType; // 动态调用GetSaver方法 var saverMethod = typeof(SubEntitySaverFactory).GetMethod(nameof(SubEntitySaverFactory.GetSaver))!.MakeGenericMethod(entityType); var saver = saverMethod.Invoke(saverFactory, null); // 动态调用SaveAsync方法 var saveMethod = saver!.GetType().GetMethod(nameof(ISubEntitySaver<object>.SaveAsync))!; await (Task)saveMethod.Invoke(saver, new object[] { prop.GetValue(domainObject), context, cancellationToken })!; } await transaction.CommitAsync(cancellationToken); } catch (Exception ex) { await transaction.RollbackAsync(cancellationToken); throw; } }
这样后续新增子实体,只需要加属性标记和对应的策略类,主逻辑完全不用改,彻底解决维护痛点。
关于建造者模式的补充
如果你的保存流程需要灵活组合(比如某些场景下不需要保存特定子实体),可以结合建造者模式来构建保存任务:
public class SaveBuilder { private readonly List<Func<Task>> _saveTasks = new(); public SaveBuilder AddSubEntity<T>(T entity, ISubEntitySaver<T> saver, YourDbContext context, CancellationToken cancellationToken) { if (entity != null) { _saveTasks.Add(() => saver.SaveAsync(entity, context, cancellationToken)); } return this; } public async Task ExecuteAsync(YourDbContext context, CancellationToken cancellationToken) { using var transaction = await context.Database.BeginTransactionAsync(cancellationToken); try { foreach (var task in _saveTasks) { await task(); } await transaction.CommitAsync(cancellationToken); } catch { await transaction.RollbackAsync(cancellationToken); throw; } } }
调用的时候就像搭积木一样:
var builder = new SaveBuilder() .AddSubEntity(domainObject.CustomerInfo, saverFactory.GetSaver<CustomerInfo>(), context, cancellationToken) .AddSubEntity(domainObject.OrderDetails, saverFactory.GetSaver<OrderDetails>(), context, cancellationToken); await builder.ExecuteAsync(context, cancellationToken);
这种方式更适合保存流程多变的场景,灵活性更强。
总结
用工厂+策略模式重构后,你会发现:
- 代码复杂度大幅降低,主逻辑从几百行缩成几十行;
- 每个子类的保存逻辑独立,调试和修改更安全;
- 新增子类只需要加策略类和注册,完全符合开闭原则;
- 事务逻辑统一管理,不用重复写try/catch和事务控制。
亲测这种方案在处理15+子类的场景下非常好用,后期维护成本直接砍半!
内容的提问来源于stack exchange,提问作者Hoser

