ASP.NET 3.1 MVC垂直切片架构:如何减少重复数据库查询?
解决方案
方案1:使用MediatR Pipeline Behavior(推荐)
利用MediatR的管道行为,在验证和处理流程之前统一查询目标记录,将结果存入请求级别的上下文对象,让Validator和Handler直接复用这条记录,避免重复查询。
实现步骤:
- 定义用于传递查询结果的上下文类:
public class RecordLookupContext { public Record DbRecord { get; set; } }
- 创建Pipeline Behavior,在管道中执行一次数据库查询并存储结果:
public class RecordLookupBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : UpdateRecordCommand.Request { private readonly IRecordRepository _repository; private readonly RecordLookupContext _lookupContext; public RecordLookupBehavior(IRecordRepository repository, RecordLookupContext lookupContext) { _repository = repository; _lookupContext = lookupContext; } public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { // 仅执行一次数据库查询 _lookupContext.DbRecord = await _repository.GetRecordById(request.Id, cancellationToken); return await next(); } }
- 修改Validator,注入上下文对象直接复用记录:
public class Validator : AbstractValidator<Request> { private readonly RecordLookupContext _lookupContext; public Validator(RecordLookupContext lookupContext) { _lookupContext = lookupContext; RuleFor(x => x) .CustomAsync(ProperRecordStatus); } private async Task ProperRecordStatus(Request request, CustomContext customContext, CancellationToken cancellationToken) { // 直接使用上下文里的记录,无需重复查询 if (_lookupContext.DbRecord == null) { customContext.AddFailure("Record Id", Errors.RecordNotExists); return; } // 其他验证逻辑也可基于_lookupContext.DbRecord实现 } }
- 修改Handler,同样注入上下文对象复用记录:
public class Handler : IRequestHandler<Request, Response> { private readonly IRecordRepository _repository; private readonly IMapper _mapper; private readonly RecordLookupContext _lookupContext; public Handler(IRecordRepository repository, IMapper mapper, RecordLookupContext lookupContext) { _repository = repository; _mapper = mapper; _lookupContext = lookupContext; } public async Task<Response> Handle(Request request, CancellationToken cancellationToken) { var record = _mapper.Map<Record>(request.Record); var dbRecord = _lookupContext.DbRecord; // 复用已查询的记录 dbRecord.Update(record); await _repository.UpdateRecord(dbRecord, cancellationToken); return new Response { Record = _mapper.Map<RecordDTO>(dbRecord) }; } }
- 在Startup.cs中注册上下文和Behavior:
services.AddScoped<RecordLookupContext>(); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RecordLookupBehavior<,>));
方案2:Handler中合并验证逻辑(简化方案)
如果不想引入Pipeline Behavior,可以直接将Validator的验证逻辑迁移到Handler中,先查询记录再做验证,验证不通过时抛出异常或返回错误响应。
修改后的Handler:
public class Handler : IRequestHandler<Request, Response> { private readonly IRecordRepository _repository; private readonly IMapper _mapper; public Handler(IRecordRepository repository, IMapper mapper) { _repository = repository; _mapper = mapper; } public async Task<Response> Handle(Request request, CancellationToken cancellationToken) { // 仅执行一次数据库查询 var dbRecord = await _repository.GetRecordById(request.Id, cancellationToken); // 执行原Validator的验证逻辑 if (dbRecord == null) { throw new ValidationException(new[] { new ValidationFailure("Record Id", Errors.RecordNotExists) }); // 也可根据全局错误策略返回包含错误的Response } var record = _mapper.Map<Record>(request.Record); dbRecord.Update(record); await _repository.UpdateRecord(dbRecord, cancellationToken); return new Response { Record = _mapper.Map<RecordDTO>(dbRecord) }; } }
该方案适合简单场景,但会让Handler承担验证职责,不符合单一职责原则,大型项目更推荐方案1。
方案3:使用请求级缓存类
定义一个Scoped生命周期的缓存类,存储当前请求中的查询结果,Validator查询后存入缓存,Handler直接从缓存读取。
实现步骤:
- 定义请求级缓存类:
public class RequestScopedCache { private readonly Dictionary<string, object> _cache = new(); public void Set<T>(string key, T value) => _cache[key] = value; public T Get<T>(string key) => _cache.TryGetValue(key, out var value) ? (T)value : default; }
- 在Startup.cs中注册为Scoped:
services.AddScoped<RequestScopedCache>();
- 修改Validator,查询后存入缓存:
public class Validator : AbstractValidator<Request> { private readonly IRecordRepository _repository; private readonly RequestScopedCache _cache; public Validator(IRecordRepository repository, RequestScopedCache cache) { _repository = repository; _cache = cache; RuleFor(x => x) .CustomAsync(ProperRecordStatus); } private async Task ProperRecordStatus(Request request, CustomContext customContext, CancellationToken cancellationToken) { var cacheKey = $"Record_{request.Id}"; var dbRecord = await _repository.GetRecordById(request.Id, cancellationToken); _cache.Set(cacheKey, dbRecord); if (dbRecord == null) { customContext.AddFailure("Record Id", Errors.RecordNotExists); return; } } }
- 修改Handler,从缓存读取记录(增加兜底查询避免缓存为空的极端情况):
public class Handler : IRequestHandler<Request, Response> { private readonly IRecordRepository _repository; private readonly IMapper _mapper; private readonly RequestScopedCache _cache; public Handler(IRecordRepository repository, IMapper mapper, RequestScopedCache cache) { _repository = repository; _mapper = mapper; _cache = cache; } public async Task<Response> Handle(Request request, CancellationToken cancellationToken) { var cacheKey = $"Record_{request.Id}"; var dbRecord = _cache.Get<Record>(cacheKey); // 兜底逻辑:若缓存无数据则重新查询 if (dbRecord == null) { dbRecord = await _repository.GetRecordById(request.Id, cancellationToken); } var record = _mapper.Map<Record>(request.Record); dbRecord.Update(record); await _repository.UpdateRecord(dbRecord, cancellationToken); return new Response { Record = _mapper.Map<RecordDTO>(dbRecord) }; } }
内容的提问来源于stack exchange,提问作者konrad8767
相关产品推荐
相关产品推荐

