基于泛型仓储模式调用多返回类型存储过程的实现问题
解决方案
1 核心问题根源
原来的ExecuteSqlQueryOrProcedure方法绑定了仓储基类的类级泛型参数T,只能返回固定类型,通过加类泛型参数T1/T2的方式扩展性极差,新增返回类型就要修改基类定义,完全不可行。
2 可行实现方案
用方法级泛型实现动态指定返回类型,不需要修改基类的泛型参数定义,兼容任意多的返回实体类型。
2.1 修改IRepositoryBase接口
// 接口原来的泛型定义保留,新增带方法级泛型的SQL执行方法 public interface IRepositoryBase<T> where T : class { // 原有方法保留不变 IQueryable<T> FindAll(bool trackChanges); IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression, bool trackChanges); void Create(T entity); void Update(T entity); void Delete(T entity); // 新增带方法级泛型的SQL执行方法,支持任意返回类型 IQueryable<TResult> ExecuteSqlQueryOrProcedure<TResult>(string sql, params object[] parameters) where TResult : class; }
建议删除原来绑定类泛型的
ExecuteSqlQueryOrProcedure方法,避免调用混淆。
2.2 修改RepositoryBase基类实现
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class { protected RepositoryContext RepositoryContext; protected DbSet<T> dbSet; public RepositoryBase(RepositoryContext repositoryContext) { RepositoryContext = repositoryContext; dbSet = RepositoryContext.Set<T>(); } // 原有CRUD方法全部保留不变 public IQueryable<T> FindAll(bool trackChanges) => !trackChanges ? RepositoryContext.Set<T>().AsNoTracking() : RepositoryContext.Set<T>(); public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression, bool trackChanges) => !trackChanges ? RepositoryContext.Set<T>().Where(expression).AsNoTracking() : RepositoryContext.Set<T>().Where(expression); public void Create(T entity) => dbSet.Add(entity); public void Update(T entity) => dbSet.Update(entity); public void Delete(T entity) => dbSet.Remove(entity); // 实现带方法级泛型的SQL执行方法 public IQueryable<TResult> ExecuteSqlQueryOrProcedure<TResult>(string sql, params object[] parameters) where TResult : class { return RepositoryContext.Set<TResult>().FromSqlRaw(sql, parameters); } }
注意:如果不需要在基类绑定固定的主实体
T,也可以直接去掉类级泛型,所有操作都用方法级泛型实现,灵活性更高。
2.3 配置无键实体(必做)
存储过程返回的查询模型通常不需要绑定数据表,也不需要主键,需要在你的RepositoryContext中配置为无键实体,否则EF调用时会报错:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 给所有存储过程返回的模型配置无键 modelBuilder.Entity<SurveyStatistics>().HasNoKey(); modelBuilder.Entity<SurveyCropTypeWise>().HasNoKey(); // 其他表的配置保留不变 }
2.4 修改具体仓储实现的调用代码
首先你的SurveyDashboardRepository不需要再定义多泛型参数,直接绑定主实体(如果没有主实体可以随便选一个,或者改成无类级泛型的仓储基类):
public class SurveyDashboardRepository : RepositoryBase<SurveyStatistics>, ISurveyDashboardRepository { public SurveyDashboardRepository(RepositoryContext repositoryContext) : base(repositoryContext) { } public async Task<IList<SurveyStatistics>> StatisticsAsync(SurveyStatisticsDto dto) { // 修正SQL注入风险,用参数化查询替代字符串拼接 var parameters = new List<SqlParameter> { new SqlParameter("@Flag", "GTY"), new SqlParameter("@ForTheYear", dto.ForTheYear) }; if (dto.PlantCode.HasValue) { parameters.Add(new SqlParameter("@PlantCode", dto.PlantCode.Value)); } var query = "Exec CN_RPT_SurveyDashBoard @Flag, @ForTheYear" + (dto.PlantCode.HasValue ? ", @PlantCode" : ""); // 调用时指定返回类型,或者省略编译器会自动推断 return await ExecuteSqlQueryOrProcedure<SurveyStatistics>(query, parameters.ToArray()).ToListAsync(); } public async Task<IList<SurveyCropTypeWise>> CropTypeWiseAsync(SurveyCropTypeWiseDto dto) { // 同样用参数化查询 var parameters = new List<SqlParameter> { new SqlParameter("@Flag", "CT"), new SqlParameter("@ForTheYear", dto.ForTheYear) }; if (dto.PlantCode.HasValue) { parameters.Add(new SqlParameter("@PlantCode", dto.PlantCode.Value)); } var query = "Exec CN_RPT_SurveyDashBoard @Flag, @ForTheYear" + (dto.PlantCode.HasValue ? ", @PlantCode" : ""); // 现在可以正常指定返回类型,不会报错 return await ExecuteSqlQueryOrProcedure<SurveyCropTypeWise>(query, parameters.ToArray()).ToListAsync(); } }
3 方案优势
- 不需要修改基类泛型定义,后续新增任意返回类型都不需要调整基类代码
- 完全兼容现有CRUD逻辑,不需要修改原有业务代码
- 支持参数化查询,避免SQL注入风险
- 扩展性强,后续如果有其他返回类型直接调用时指定泛型参数即可
内容的提问来源于stack exchange,提问作者Bhanu Pratap singh
相关产品推荐
相关产品推荐

