如何重构多数据集后端代码以降低维护成本?
代码重构方案:减少数据集Command/Query重复代码
针对你提到的40个数据集表单重复创建Command/Query文件、维护成本高的问题,以下是几个实用的重构方案,按落地优先级排序:
1. 分组抽象+泛型模板方法
既然全局基类因字段差异不适用,那就按数据集组(比如IDCards组)做分层抽象,结合泛型复用通用逻辑:
实现步骤
- 先按业务组定义组级基类,把组内数据集共有的字段、验证逻辑放在这里:
// IDCards组的基类,包含组内通用字段 public abstract class BaseIDCardCommand<TEntity> : BaseCreateCommand<TEntity> where TEntity : class, new() { public int CountryID { get; set; } // 组内共用字段 public string IDNumber { get; set; } } - 再定义全局泛型基类,封装所有数据集通用的CRUD逻辑(比如持久化、基础验证):
public abstract class BaseCreateCommand<TEntity> : IRequest<int> where TEntity : class, new() { public string CreatedBy { get; set; } public DateTime CreatedTime { get; set; } = DateTime.UtcNow; // 模板方法:子类实现字段到实体的映射 public abstract TEntity MapToEntity(); // 通用验证逻辑 public virtual async Task<ValidationResult> ValidateAsync(ValidationContext context) { var result = new ValidationResult(); if (string.IsNullOrEmpty(CreatedBy)) { result.Errors.Add(new ValidationFailure(nameof(CreatedBy), "创建人不能为空")); } return result; } } - 每个数据集的Command只需要继承对应组的基类,实现专属字段和逻辑:
public class CreateValidIDCardsCommand : BaseIDCardCommand<ValidIDCards> { // 专属字段:比如本国身份证特有的户籍地址 public string HouseholdAddress { get; set; } public override ValidIDCards MapToEntity() { return new ValidIDCards { CountryID = CountryID, IDNumber = IDNumber, HouseholdAddress = HouseholdAddress, CreatedBy = CreatedBy, CreatedTime = CreatedTime }; } // 专属验证:比如户籍地址格式检查 public override async Task<ValidationResult> ValidateAsync(ValidationContext context) { var baseResult = await base.ValidateAsync(context); if (string.IsNullOrEmpty(HouseholdAddress)) { baseResult.Errors.Add(new ValidationFailure(nameof(HouseholdAddress), "户籍地址不能为空")); } return baseResult; } }
优点
- 类型安全,编译时就能发现错误;
- 修改组内共用字段(比如CountryID类型)时,只需要修改组基类,不用改所有子类;
- 保留每个数据集的专属逻辑灵活性。
2. 代码生成器(T4模板/Source Generator)
如果新增数据集的重复工作占比高,直接用代码生成器批量生成Command、Query、Handler文件,从根源上消除重复:
实现思路
- 定义元数据:可以用数据库表结构、JSON配置文件或者Excel表格,记录每个数据集的名称、字段列表、类型;
- 用T4模板或.NET Source Generator读取元数据,自动生成全套CRUD代码:
- T4模板适合手动触发生成,修改方便;
- Source Generator适合实时生成,编译时自动更新代码。
示例T4模板片段
<#@ template language="C#" #> <#@ parameter name="EntityName" type="string" #> <#@ parameter name="CommonFields" type="List<FieldInfo>" #> <#@ parameter name="UniqueFields" type="List<FieldInfo>" #> // 自动生成的Create命令 public class Create<#= EntityName #>Command : IRequest<int> { <# foreach(var field in CommonFields) { #> public <#= field.Type #> <#= field.Name #> { get; set; } <# } #> <# foreach(var field in UniqueFields) { #> public <#= field.Type #> <#= field.Name #> { get; set; } <# } #> } // 自动生成的Create命令Handler public class Create<#= EntityName #>CommandHandler : IRequestHandler<Create<#= EntityName #>Command, int> { private readonly AppDbContext _dbContext; public Create<#= EntityName #>CommandHandler(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<int> Handle(Create<#= EntityName #>Command request, CancellationToken cancellationToken) { var entity = new <#= EntityName #> { <# foreach(var field in CommonFields.Concat(UniqueFields)) { #> <#= field.Name #> = request.<#= field.Name #>, <# } #> CreatedBy = request.CreatedBy, CreatedTime = DateTime.UtcNow }; _dbContext.<#= EntityName #>.Add(entity); await _dbContext.SaveChangesAsync(cancellationToken); return entity.Id; } }
优点
- 新增数据集只需要更新元数据,一键生成所有代码;
- 修改字段类型时,更新元数据后重新生成,不用手动修改几十上百个文件;
- 代码风格统一,避免手动编写的错误。
3. 动态DTO+通用Handler(适合快速迭代场景)
如果对类型安全要求不高,且字段变化频繁,可以用动态对象+反射实现通用的CRUD逻辑,完全避免重复创建Command/Query文件:
实现示例
// 通用创建命令 public class GenericCreateCommand : IRequest<int> { public string EntityName { get; set; } // 数据集实体名称 public Dictionary<string, object> Fields { get; set; } // 字段键值对 public string CreatedBy { get; set; } } // 通用创建命令Handler public class GenericCreateCommandHandler : IRequestHandler<GenericCreateCommand, int> { private readonly AppDbContext _dbContext; public GenericCreateCommandHandler(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<int> Handle(GenericCreateCommand request, CancellationToken cancellationToken) { // 通过反射获取实体类型 var entityType = Assembly.GetExecutingAssembly().GetTypes() .First(t => t.Name == request.EntityName); var entity = Activator.CreateInstance(entityType); // 设置通用字段 entityType.GetProperty("CreatedBy")?.SetValue(entity, request.CreatedBy); entityType.GetProperty("CreatedTime")?.SetValue(entity, DateTime.UtcNow); // 设置自定义字段 foreach (var field in request.Fields) { var prop = entityType.GetProperty(field.Key); if (prop != null) { var value = Convert.ChangeType(field.Value, prop.PropertyType); prop.SetValue(entity, value); } } // 添加到DbSet并保存 var dbSet = _dbContext.GetType().GetMethod("Set").MakeGenericMethod(entityType).Invoke(_dbContext, null); dbSet.GetType().GetMethod("Add").Invoke(dbSet, new[] { entity }); await _dbContext.SaveChangesAsync(cancellationToken); return (int)entityType.GetProperty("Id").GetValue(entity); } }
优缺点
- 优点:无需为每个数据集编写Command/Handler,新增数据集零代码;
- 缺点:编译时无类型检查,容易出现字段名称/类型错误,调试难度高,不适合大型生产系统。
实践建议
优先采用分组抽象+代码生成器的组合方案:
- 先按业务组定义基类,封装组内通用逻辑;
- 用代码生成器基于基类生成每个数据集的专属代码;
- 后续修改共用字段时,只需要更新组基类和元数据,重新生成代码即可。
内容的提问来源于stack exchange,提问作者Lube
相关产品推荐
相关产品推荐

