You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#业务层开发中子对象校验时机与最优实现方案咨询

解决方案

一、子对象校验复用方案

核心思路是校验逻辑与存储逻辑解耦,抽离独立的可复用校验组件,避免重复编写校验规则:

  1. 定义通用校验接口
public interface IValidator<T>
{
    // 校验方法,根据需求传入必要的上下文参数(比如当前用户)
    void Validate(T entity, User user = null);
}
  1. 分别实现各实体的独立校验器
// 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);
        }
    }
}
  1. 改造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随意提交全局变更:

  1. 定义工作单元接口
public interface IUnitOfWork : IDisposable
{
    DbContext Context { get; }
    // 统一提交所有变更的入口
    Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
    // 可选:事务控制方法,应对需要多操作原子提交的场景
    Task BeginTransactionAsync();
    Task CommitTransactionAsync();
    Task RollbackTransactionAsync();
}
  1. 工作单元实现
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();
    }
}
  1. 改造控制器调用逻辑,统一控制提交时机:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 16:15:05