如何在Jason Taylor的Clean Architecture中调用存储过程?
在Jason Taylor的Clean Architecture中调用存储过程的实现方案
结合NorthwindTraders的架构分层设计,调用存储过程需要遵循依赖倒置原则,将具体实现放在Infrastructure层,Core层定义抽象接口,以下是具体步骤:
1. 在Core层定义抽象接口
在Core层的接口目录下,针对业务需求定义包含存储过程调用逻辑的抽象方法,同时定义用于接收存储过程返回数据的DTO(需放在Core层的DTO目录下):
// Core/Interfaces/IOrderRepository.cs using Core.Dtos; using System.Collections.Generic; using System.Threading.Tasks; public interface IOrderRepository { Task<IEnumerable<OrderStatsDto>> GetOrderStatsAsync(DateTime startDate, DateTime endDate); } // Core/Dtos/OrderStatsDto.cs public class OrderStatsDto { public int OrderCount { get; set; } public decimal TotalRevenue { get; set; } public string Category { get; set; } }
2. 在Infrastructure层实现接口并调用存储过程
Infrastructure层负责数据访问,通过EF Core的FromSqlRaw(有返回值)或ExecuteSqlRawAsync(无返回值)方法调用存储过程,同时利用参数化查询避免SQL注入:
// Infrastructure/Persistence/Repositories/OrderRepository.cs using Core.Dtos; using Core.Interfaces; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Data.SqlClient; using System.Threading.Tasks; public class OrderRepository : IOrderRepository { private readonly NorthwindDbContext _context; public OrderRepository(NorthwindDbContext context) { _context = context; } public async Task<IEnumerable<OrderStatsDto>> GetOrderStatsAsync(DateTime startDate, DateTime endDate) { // 定义参数,确保类型匹配 var startParam = new SqlParameter("@StartDate", startDate); var endParam = new SqlParameter("@EndDate", endDate); // 调用存储过程,映射到DTO return await _context.Set<OrderStatsDto>() .FromSqlRaw("EXEC GetOrderStatsByDateRange @StartDate, @EndDate", startParam, endParam) .ToListAsync(); } // 示例:执行无返回值的存储过程 public async Task UpdateOrderStatusAsync(int orderId, string newStatus) { var orderIdParam = new SqlParameter("@OrderId", orderId); var statusParam = new SqlParameter("@NewStatus", newStatus); await _context.Database.ExecuteSqlRawAsync("EXEC UpdateOrderStatus @OrderId, @NewStatus", orderIdParam, statusParam); } }
如果返回的DTO不是EF Core实体,需要在DbContext中配置为无键实体:
// Infrastructure/Persistence/NorthwindDbContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置无键实体,用于存储过程返回结果映射 modelBuilder.Entity<OrderStatsDto>().HasNoKey(); // 其他实体配置... }
3. 注册依赖注入
在Infrastructure层的DependencyInjection类中,将实现类与Core层的接口绑定:
// Infrastructure/DependencyInjection.cs using Core.Interfaces; using Infrastructure.Persistence.Repositories; using Microsoft.Extensions.DependencyInjection; public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services) { // 注册DbContext... // 注册仓储实现 services.AddScoped<IOrderRepository, OrderRepository>(); return services; } }
4. 在Application层调用
通过MediatR的查询/命令处理器注入Core层接口,调用存储过程逻辑:
// Application/Orders/Queries/GetOrderStats/GetOrderStatsQuery.cs using Core.Dtos; using Core.Interfaces; using MediatR; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; public class GetOrderStatsQuery : IRequest<IEnumerable<OrderStatsDto>> { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } public class GetOrderStatsQueryHandler : IRequestHandler<GetOrderStatsQuery, IEnumerable<OrderStatsDto>> { private readonly IOrderRepository _orderRepository; public GetOrderStatsQueryHandler(IOrderRepository orderRepository) { _orderRepository = orderRepository; } public async Task<IEnumerable<OrderStatsDto>> Handle(GetOrderStatsQuery request, CancellationToken cancellationToken) { return await _orderRepository.GetOrderStatsAsync(request.StartDate, request.EndDate); } }
关键注意事项
- 始终使用参数化查询,禁止直接拼接SQL字符串,避免SQL注入风险。
- 存储过程的参数名称、类型需与代码中的参数严格匹配。
- 若存储过程返回多个结果集,可使用
_context.Database.ExecuteSqlRawAsync结合SqlDataReader手动处理。
内容的提问来源于stack exchange,提问作者Aman Rohilla
相关产品推荐
相关产品推荐

