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

ASP.NET 3.1 MVC垂直切片架构:如何减少重复数据库查询?

解决方案

方案1:使用MediatR Pipeline Behavior(推荐)

利用MediatR的管道行为,在验证和处理流程之前统一查询目标记录,将结果存入请求级别的上下文对象,让Validator和Handler直接复用这条记录,避免重复查询。

实现步骤:

  1. 定义用于传递查询结果的上下文类:
public class RecordLookupContext
{
    public Record DbRecord { get; set; }
}
  1. 创建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();
    }
}
  1. 修改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实现
    }
}
  1. 修改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) };
    }
}
  1. 在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直接从缓存读取。

实现步骤:

  1. 定义请求级缓存类:
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;
}
  1. 在Startup.cs中注册为Scoped:
services.AddScoped<RequestScopedCache>();
  1. 修改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;
        }
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:44:54