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

基于泛型仓储模式调用多返回类型存储过程的实现问题

解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 05:18:02