FluentValidation验证集合前异步预加载数据的方案问询
解决方案
核心思路是通过FluentValidation的CustomAsync规则预加载全局验证数据到RootContextData,再在单个项的验证器中复用这份数据,同时结合DependentRules保证限流逻辑优先执行。具体实现如下:
1. 父验证器结构:限流+预加载+项验证
先定义限流规则,只有通过限流后才执行数据库相关的预加载和项验证:
public class ParentModelValidator : AbstractValidator<ParentModel> { private readonly YourDbContext _dbContext; public ParentModelValidator(YourDbContext dbContext) { _dbContext = dbContext; // 限流规则:限制子项数量,防止DoS攻击 RuleFor(r => r.Children) .Must(c => c == null || c.Count() <= 100) .WithMessage("子项数量不能超过100个"); // 限流通过后执行数据库相关验证逻辑 DependentRules(() => { // 异步预加载所有子项需要的数据库数据,存入RootContextData RuleFor(r => r.Children) .CustomAsync(async (children, context, cancellationToken) => { if (children == null || !children.Any()) return; // 提取子项中需要验证的标识(比如ID) var childIds = children.Select(c => c.Id).ToList(); // 单次数据库查询获取所有有效数据 var validChildIds = await _dbContext.ValidChildRecords .Where(record => childIds.Contains(record.Id)) .Select(record => record.Id) .ToListAsync(cancellationToken); // 将预加载数据存入全局上下文,供项验证器使用 context.RootContextData["ValidChildIds"] = validChildIds; }); // 对每个子项应用验证器,复用预加载的数据 RuleForEach(r => r.Children) .SetAsyncValidator(new ChildItemValidator()); }); } }
2. 子项验证器:复用预加载数据
子项验证器不再直接查询数据库,而是从RootContextData中获取预加载的全局数据,为每个无效项单独添加错误:
public class ChildItemValidator : AsyncPropertyValidator<ParentModel, Child> { public override async Task<bool> IsValidAsync(ValidationContext<ParentModel> context, Child child, CancellationToken cancellationToken) { // 从全局上下文获取预加载的有效ID列表 if (!context.RootContextData.TryGetValue("ValidChildIds", out var data) || data is not List<int> validIds) { context.AddFailure(nameof(ParentModel.Children), "验证数据加载失败"); return false; } // 验证当前子项是否有效 if (!validIds.Contains(child.Id)) { // 为当前子项添加独立错误,路径自动关联到集合中的具体项(如Children[0]) var itemPath = context.PropertyChain.ToString(); context.AddFailure(itemPath, $"子项ID {child.Id} 不存在于系统中"); return false; } return true; } public override string Name => nameof(ChildItemValidator); }
方案优势
- 减少数据库往返:仅执行一次异步数据库查询,所有子项复用同一份数据。
- 独立错误报告:每个无效子项会生成单独的验证错误,包含具体项的路径,便于客户端定位问题。
- 安全限流:通过
DependentRules保证只有通过限流验证后,才会触发数据库操作,避免恶意请求耗尽资源。
注意事项
- 处理集合为空的场景,避免不必要的数据库查询。
RootContextData的键需唯一,避免与其他验证逻辑冲突。- 若预加载数据失败(如数据库异常),可在
CustomAsync中直接添加全局错误,终止后续验证。
内容的提问来源于stack exchange,提问作者Gavin
相关产品推荐
相关产品推荐

