如何在Fluent Validation中避免重复从数据库检索数据?
在Fluent Validation中复用数据库查询结果的方案
针对你遇到的多个验证规则重复查询数据库的问题,这里有两种实用的解决思路,均基于单次查询+请求级缓存实现:
方案1:利用ValidationContext.Items做请求级缓存
ValidationContext.Items是每个验证请求独有的字典,你可以在第一个需要数据库数据的规则中执行查询,将结果存入该字典,后续所有规则直接从字典读取数据,避免重复查询。
示例代码:
public class OrderValidator : AbstractValidator<Order> { private readonly IUserRepository _userRepo; public OrderValidator(IUserRepository userRepo) { _userRepo = userRepo; // 第一个规则:查询并缓存用户限额 RuleFor(x => x.UserId) .MustAsync(async (userId, context, cancellationToken) => { if (!context.Items.TryGetValue("UserLimit", out var limitObj)) { var userLimit = await _userRepo.GetUserLimitAsync(userId, cancellationToken); context.Items["UserLimit"] = userLimit; } // 验证用户是否存在/有可用限额 return limitObj != null; }) .WithMessage("用户不存在或无可用限额"); // 第二个规则:复用缓存数据验证订单金额 RuleFor(x => x.TotalAmount) .Must((order, amount, context) => { if (context.Items.TryGetValue("UserLimit", out var limitObj) && limitObj is UserLimit userLimit) { return amount <= userLimit.MaxOrderAmount; } return false; }) .WithMessage("订单金额超过用户最大限额"); // 第三个规则:复用缓存数据验证商品数量 RuleFor(x => x.ItemCount) .Must((order, count, context) => { if (context.Items.TryGetValue("UserLimit", out var limitObj) && limitObj is UserLimit userLimit) { return count <= userLimit.MaxItemCount; } return false; }) .WithMessage("订单商品数量超过用户最大限制"); } }
方案2:封装共享数据的获取逻辑
把缓存+查询的逻辑抽成私有方法,所有验证规则调用这个方法,代码更整洁,避免重复的缓存判断逻辑。
示例代码:
public class OrderValidator : AbstractValidator<Order> { private readonly IUserRepository _userRepo; public OrderValidator(IUserRepository userRepo) { _userRepo = userRepo; RuleFor(x => x.UserId) .MustAsync(ValidateUserHasValidLimit) .WithMessage("用户不存在或无可用限额"); RuleFor(x => x.TotalAmount) .MustAsync(ValidateAmountWithinLimit) .WithMessage("订单金额超过用户最大限额"); RuleFor(x => x.ItemCount) .MustAsync(ValidateItemCountWithinLimit) .WithMessage("订单商品数量超过用户最大限制"); } private async Task<bool> ValidateUserHasValidLimit(string userId, ValidationContext<Order> context, CancellationToken cancellationToken) { var userLimit = await GetCachedUserLimit(userId, context, cancellationToken); return userLimit != null; } private async Task<bool> ValidateAmountWithinLimit(Order order, decimal amount, ValidationContext<Order> context, CancellationToken cancellationToken) { var userLimit = await GetCachedUserLimit(order.UserId, context, cancellationToken); return userLimit != null && amount <= userLimit.MaxOrderAmount; } private async Task<bool> ValidateItemCountWithinLimit(Order order, int count, ValidationContext<Order> context, CancellationToken cancellationToken) { var userLimit = await GetCachedUserLimit(order.UserId, context, cancellationToken); return userLimit != null && count <= userLimit.MaxItemCount; } // 核心逻辑:每个验证请求只查询一次数据库 private async Task<UserLimit> GetCachedUserLimit(string userId, ValidationContext<Order> context, CancellationToken cancellationToken) { var cacheKey = $"UserLimit_{userId}"; if (context.Items.TryGetValue(cacheKey, out var limitObj) && limitObj is UserLimit userLimit) { return userLimit; } var newLimit = await _userRepo.GetUserLimitAsync(userId, cancellationToken); context.Items[cacheKey] = newLimit; return newLimit; } }
关键注意事项
- 不要在Validator类的字段中存储请求级数据:如果Validator被注册为单例(比如ASP.NET Core中的
AddSingleton),字段会被多个请求共享,引发并发问题;即使用AddScoped注册,也推荐用ValidationContext.Items,因为同一个Scope可能处理多个验证请求。 - 异步验证要保持一致:所有用到共享数据的规则尽量用
MustAsync或CustomAsync,避免同步异步混用导致的异常。
内容的提问来源于stack exchange,提问作者Giulia Meo
相关产品推荐
相关产品推荐

