FastEndpoints:Data类的用法及查询代码编写位置
FastEndpoints Data类用法与数据库查询位置指南
一、Data类的正确用法与示例
FastEndpoints中Endpoint<TRequest, TResponse, TData>里的TData,是用来在端点的生命周期方法间共享临时状态的容器,和数据库查询没有直接关联。
适用场景
- 需要在
PreProcessAsync(前置处理)、HandleAsync(核心逻辑)、PostProcessAsync(后置处理)之间传递数据时 - 存储端点内的中间计算结果、验证后的上下文数据等,避免重复计算或繁琐的参数传递
示例代码
比如一个获取用户详情的端点,在前置处理中提前验证用户ID并查询基础信息,存入Data供核心逻辑使用:
public class GetUserDetailsEndpoint : Endpoint<GetUserRequest, GetUserResponse, UserContextData> { private readonly AppDbContext _dbContext; public GetUserDetailsEndpoint(AppDbContext dbContext) { _dbContext = dbContext; } public override void Configure() { Get("/users/{UserId}"); AllowAnonymous(); } // 前置处理:验证ID并查询用户基础数据 public override async Task PreProcessAsync(CancellationToken ct) { var userId = Route<int>("UserId"); var user = await _dbContext.Users.FindAsync(userId, ct); if (user == null) { AddError("用户不存在"); ThrowIfAnyErrors(); } // 将查询结果存入Data,供后续方法调用 Data.User = user; Data.RequestedUserId = userId; } // 核心逻辑:使用Data中的数据构建响应 public override async Task HandleAsync(GetUserRequest req, CancellationToken ct) { Response = new GetUserResponse { Id = Data.RequestedUserId, Name = Data.User.Name, Email = Data.User.Email, JoinDate = Data.User.JoinDate }; } } // 自定义Data类,存储端点内需要共享的状态 public class UserContextData { public User User { get; set; } public int RequestedUserId { get; set; } } // 请求/响应类 public class GetUserRequest { } public class GetUserResponse { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public DateTime JoinDate { get; set; } }
二、数据库查询的位置选择
1. 简单查询:直接写在Endpoint中
如果查询逻辑简单(单表查询、无复杂关联),且仅当前端点使用,直接写在HandleAsync或PreProcessAsync中完全符合垂直切片架构。垂直切片的核心是围绕业务功能聚合代码,不需要为了“分层”而强行抽象。
示例:
public override async Task HandleAsync(GetUserRequest req, CancellationToken ct) { var user = await _dbContext.Users .Where(u => u.Id == req.UserId) .Select(u => new GetUserResponse { Id = u.Id, Name = u.Name }) .FirstOrDefaultAsync(ct); if (user == null) ThrowError("用户不存在"); Response = user; }
2. 复杂/复用查询:使用业务切片专属仓储
如果查询逻辑复杂(多表关联、过滤规则复杂),或者多个端点需要复用相同的查询逻辑,不要用通用的IRepository<T>(这种围绕实体的抽象违背垂直切片),而是创建属于当前业务切片的专属仓储。
比如用户模块下多个端点需要查询用户订单信息,就创建UserOrderRepository,仅包含该切片需要的查询方法:
// 专属仓储:仅服务于用户相关的订单查询 public class UserOrderRepository { private readonly AppDbContext _dbContext; public UserOrderRepository(AppDbContext dbContext) { _dbContext = dbContext; } // 仅实现当前业务需要的查询方法 public async Task<List<UserOrderDto>> GetUserRecentOrders(int userId, int count, CancellationToken ct) { return await _dbContext.Orders .Where(o => o.UserId == userId && o.CreatedAt >= DateTime.Now.AddDays(-30)) .OrderByDescending(o => o.CreatedAt) .Take(count) .Select(o => new UserOrderDto { OrderId = o.Id, TotalAmount = o.TotalAmount, CreatedAt = o.CreatedAt }) .ToListAsync(ct); } }
然后在Endpoint中注入使用:
public class GetUserRecentOrdersEndpoint : Endpoint<GetUserOrdersRequest, GetUserOrdersResponse> { private readonly UserOrderRepository _orderRepo; public GetUserRecentOrdersEndpoint(UserOrderRepository orderRepo) { _orderRepo = orderRepo; } public override void Configure() { Get("/users/{UserId}/recent-orders"); AllowAnonymous(); } public override async Task HandleAsync(GetUserOrdersRequest req, CancellationToken ct) { var orders = await _orderRepo.GetUserRecentOrders(req.UserId, req.Count, ct); Response = new GetUserOrdersResponse { Orders = orders }; } }
为什么专属仓储不违背垂直切片?
垂直切片禁止的是跨切片的通用抽象层(比如包含所有实体CRUD的通用仓储),而专属仓储是当前业务切片的一部分,它的方法都是为当前切片的业务功能服务的,没有跨切片依赖,完全符合“围绕业务功能聚合代码”的原则。
内容的提问来源于stack exchange,提问作者Peter Chovanec
相关产品推荐
相关产品推荐

