C#业务层开发中子对象校验时机与最优实现方案咨询
解决方案
一、子对象校验复用方案
核心思路是校验逻辑与存储逻辑解耦,抽离独立的可复用校验组件,避免重复编写校验规则:
- 定义通用校验接口
public interface IValidator<T> { // 校验方法,根据需求传入必要的上下文参数(比如当前用户) void Validate(T entity, User user = null); }
- 分别实现各实体的独立校验器
// Invoice校验器实现 public class InvoiceValidator : IValidator<Invoice> { private readonly IValidator<InvoiceDetails> _detailValidator; // 注入子对象的校验器,直接复用规则 public InvoiceValidator(IValidator<InvoiceDetails> detailValidator) { _detailValidator = detailValidator; } public void Validate(Invoice invoice, User user) { // 原有Invoice自身的校验规则 if (!user.canSaveInvoices) { throw new Exception("User cannot save invoices!!"); } // 遍历子对象,直接复用已有的明细校验规则 foreach (var detail in invoice.Details) { _detailValidator.Validate(detail); } } } // InvoiceDetails校验器实现 public class InvoiceDetailsValidator : IValidator<InvoiceDetails> { public void Validate(InvoiceDetails detail, User user = null) { // 原有明细自身的校验规则 if (detail.Amount < 0) { throw new Exception("Amount cannot be less than 0!!"); } // 如果明细还有嵌套子明细,同样可以注入自身的校验器递归校验 foreach (var subDetail in detail.Details) { Validate(subDetail); } } }
- 改造Service层,移除Save方法里硬编码的校验逻辑,改为注入对应校验器调用,校验逻辑完全复用:
public class InvoiceService { private readonly IValidator<Invoice> _validator; private readonly DbContext _context; public InvoiceService(IValidator<Invoice> validator, DbContext context) { _validator = validator; _context = context; } public async Task<Invoice> Save(Invoice invoice, User user) { // 调用校验器完成所有校验(包含子对象校验) _validator.Validate(invoice, user); if (invoice.Id == 0) { _context.Invoices.Add(invoice); } // 注意这里先不要调用SaveChanges,交给下文的工作单元统一提交 return invoice; } }
这个方案下,不管是InvoiceDetails作为子对象被Invoice关联,还是单独在其他场景下被调用,都可以直接注入IValidator<InvoiceDetails>复用校验规则;后续Invoice作为其他上层对象的子对象时,上层校验器只要注入IValidator<Invoice>即可复用所有Invoice相关的校验逻辑,不需要重复编写。
二、SaveChanges全局提交问题优化
核心思路是引入工作单元(Unit Of Work)模式,统一管理上下文生命周期和事务提交,避免单个Service随意提交全局变更:
- 定义工作单元接口
public interface IUnitOfWork : IDisposable { DbContext Context { get; } // 统一提交所有变更的入口 Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); // 可选:事务控制方法,应对需要多操作原子提交的场景 Task BeginTransactionAsync(); Task CommitTransactionAsync(); Task RollbackTransactionAsync(); }
- 工作单元实现
public class UnitOfWork : IUnitOfWork { private readonly DbContext _context; private IDbContextTransaction _transaction; public UnitOfWork(DbContext context) { _context = context; } public DbContext Context => _context; public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { return await _context.SaveChangesAsync(cancellationToken); } public async Task BeginTransactionAsync() { _transaction = await _context.Database.BeginTransactionAsync(); } public async Task CommitTransactionAsync() { await _transaction.CommitAsync(); await _transaction.DisposeAsync(); } public async Task RollbackTransactionAsync() { await _transaction.RollbackAsync(); await _transaction.DisposeAsync(); } public void Dispose() { _transaction?.Dispose(); _context.Dispose(); } }
- 改造控制器调用逻辑,统一控制提交时机:
public class InvoiceController : ControllerBase { private readonly InvoiceService _invoiceService; private readonly IUnitOfWork _unitOfWork; public InvoiceController(InvoiceService invoiceService, IUnitOfWork unitOfWork) { _invoiceService = invoiceService; _unitOfWork = unitOfWork; } public async Task<IActionResult> CreateInvoice([FromBody] InvoiceRequest model) { var invoiceDetails = new InvoiceDetails() { //fill properties }; var invoice = new Invoice() { //fill properties Details = new List<InvoiceDetails>() { invoiceDetails }; }; // 调用Service完成实体添加和校验,不会触发提交 await _invoiceService.Save(invoice, currentUser); // 所有业务操作完成后,统一提交一次,所有变更原子生效 await _unitOfWork.SaveChangesAsync(); return Ok(invoice); } }
这个方案下,所有Service共用同一个上下文实例,只有业务逻辑全部执行完成后才会统一提交变更,不会出现部分操作意外提交的问题,也便于实现多操作的原子事务控制。
内容的提问来源于stack exchange,提问作者Ale
相关产品推荐
相关产品推荐

