EF使用FromSqlRaw调用存储过程报非组合SQL错误问题咨询
报错原因
EF Core 中FromSqlRaw/FromSqlInterpolated执行带EXECUTE的存储过程时,生成的SQL属于不可组合类型,不支持在后续链式调用中拼接额外的数据库端查询逻辑(比如你代码里的ProjectTo投影操作)。EF Core默认会尝试把后续LINQ操作翻译成SQL嵌套在存储过程外层执行,但存储过程无法作为子查询被嵌套调用,因此抛出该异常。
可行实现方案
方案1:客户端评估映射(改动最小)
按照报错提示,在存储过程查询后调用AsEnumerable()切换到客户端执行后续投影逻辑,存储过程执行完成后会把结果拉到应用侧,再完成DTO映射,不会生成非法的嵌套SQL。
public async Task<ServiceResponse> GetAllPackagesByOwner(string ownerStaffNumber) { SqlParameter ownerStaffNumberParameter = new SqlParameter ("@ownerStaffNumber", ownerStaffNumber); var packages2 = await _context.PackageMaster .FromSqlRaw<PackageMaster>("EXECUTE dbo.spGetCurrentUserAssetList @ownerStaffNumber", ownerStaffNumberParameter) .AsEnumerable() .AsQueryable() .ProjectTo<PackageMasterDto>(_mapper.ConfigurationProvider) .ToListAsync(); // 补充ServiceResponse封装逻辑即可 }
如果觉得AsQueryable写法冗余,也可以先把存储过程结果查询为实体列表,再单独做映射,逻辑完全一致,可读性更高:
public async Task<ServiceResponse> GetAllPackagesByOwner(string ownerStaffNumber) { SqlParameter ownerStaffNumberParameter = new SqlParameter ("@ownerStaffNumber", ownerStaffNumber); var packageEntities = await _context.PackageMaster .FromSqlRaw<PackageMaster>("EXECUTE dbo.spGetCurrentUserAssetList @ownerStaffNumber", ownerStaffNumberParameter) .ToListAsync(); var packages2 = _mapper.Map<List<PackageMasterDto>>(packageEntities); // 补充ServiceResponse封装逻辑即可 }
方案2:原生ADO.NET执行(性能最优)
绕开EF Core的查询管道,直接通过底层数据库连接执行存储过程,手动处理结果映射,完全规避EF Core的SQL组合限制,适合数据量大、对性能要求高的场景:
public async Task<ServiceResponse> GetAllPackagesByOwner(string ownerStaffNumber) { var result = new List<PackageMasterDto>(); var conn = _context.Database.GetDbConnection(); try { using var cmd = conn.CreateCommand(); cmd.CommandText = "dbo.spGetCurrentUserAssetList"; cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.Parameters.Add(new SqlParameter("@ownerStaffNumber", ownerStaffNumber)); await conn.OpenAsync(); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { // 逐行读取结果映射为DTO,可根据返回字段灵活赋值 var dto = new PackageMasterDto { // 示例:Id = reader.GetInt32(0), PackageName = reader.GetString(1) }; result.Add(dto); } } finally { await conn.CloseAsync(); } // 基于result封装ServiceResponse即可 }
注意事项
- 不要删除存储过程前的
EXECUTE关键字强行让EF Core把存储过程当表/视图做SQL组合,会生成语法错误的SQL语句 - 使用方案1时要确保存储过程返回的字段集合和
PackageMaster实体的属性名、类型匹配,否则会出现EF Core实体映射失败的问题 - 只要是存储过程调用,后续所有分页、过滤、投影操作如果放在数据库端执行都会触发同类报错,要么把逻辑写进存储过程内部,要么切换到客户端执行
内容的提问来源于stack exchange,提问作者User338282
相关产品推荐
相关产品推荐

