如何在ASP.NET Core 6 Web API中使用EF Core 6处理存储过程输出
问题根因
EF Core 6 中针对强类型DbSet<T>的FromSqlInterpolated方法,会强制将存储过程返回的第一个结果集按照T(即你的CustomerModelDTO)的属性结构做一一映射。当前存储过程在无匹配数据时返回的单值1属于结构完全不同的结果集,和CustomerModelDTO的属性(比如报错提到的CustomerID列)完全不匹配,自然会抛出映射异常。
本质问题是存储过程返回的结果集结构不统一,属于数据访问层的典型坏实践,优先从根源修复成本最低。
方案1:调整存储过程逻辑(最推荐,无性能损耗,逻辑最清晰)
不要用返回不同结构结果集的方式区分业务状态,统一结果集结构,用输出参数传递状态码:
- 给存储过程新增一个
int类型的输出参数,用来标记是否查询到数据 - 无论有没有匹配数据,都返回和
CustomerModelDTO结构完全一致的结果集:无数据时返回列结构完全匹配的空结果集,有数据时返回正常业务记录 - 调用时通过输出参数判断业务状态,再处理结果集
存储过程调整示例:
CREATE OR ALTER PROCEDURE spName @input1 INT, @input2 NVARCHAR(100), @queryStatus INT OUTPUT -- 输出参数:0=有匹配数据,1=无匹配数据 AS BEGIN SET NOCOUNT ON; -- 必须加,屏蔽行数统计产生的额外结果干扰 IF NOT EXISTS (SELECT 1 FROM Customer WHERE /* 原有查询判断条件 */) BEGIN SET @queryStatus = 1; -- 返回和正常查询结构完全一致的空结果集 SELECT TOP 0 CustomerID, CustomerName, /* 逐行对齐CustomerModelDTO的所有映射属性 */ RETURN; END SET @queryStatus = 0; -- 原有正常查询逻辑 SELECT CustomerID, CustomerName, /* 其他属性列 */ FROM Customer WHERE /* 原有查询条件 */ END
C# 调用代码:
// 定义输出参数 var statusParam = new SqlParameter { ParameterName = "@queryStatus", SqlDbType = System.Data.SqlDbType.Int, Direction = System.Data.ParameterDirection.Output }; var customerList = _dbContext.CustomerModelDTO .FromSqlInterpolated($"exec spName {input1}, {input2}, {statusParam} OUT") .ToList(); if ((int)statusParam.Value == 1) { // 无匹配数据的业务处理逻辑,比如返回空结果、自定义提示等 } // 有数据直接使用customerList即可
方案2:无存储过程修改权限时的兼容处理
如果你没有权限调整存储过程逻辑,就不要直接用强类型DbSet做映射,手动处理多结构结果集:
List<CustomerModelDTO> result = new(); // 获取底层数据库连接做手动读取 using var connection = _dbContext.Database.GetDbConnection(); using var command = connection.CreateCommand(); command.CommandText = "spName"; command.CommandType = System.Data.CommandType.StoredProcedure; // 添加输入参数 command.Parameters.Add(new SqlParameter("@input1", input1)); command.Parameters.Add(new SqlParameter("@input2", input2)); await connection.OpenAsync(); using var reader = await command.ExecuteReaderAsync(); // 读取结果集列元数据,判断是标量值还是业务结果集 if (reader.HasRows) { var columnSchema = await reader.GetColumnSchemaAsync(); // 存在CustomerID列说明是正常业务结果集,用EF做映射 if (columnSchema.Any(col => col.ColumnName == nameof(CustomerModelDTO.CustomerID))) { reader.Close(); result = _dbContext.CustomerModelDTO .FromSqlInterpolated($"exec spName {input1}, {input2}") .ToList(); } // 否则就是返回的标量1,直接走无数据逻辑 }
额外注意
先确认存储过程里的标量1是
SELECT 1返回的结果集,还是RETURN 1返回的过程状态码:如果是RETURN 1的场景,不需要调整存储过程结构,只需要在存储过程开头加SET NOCOUNT ON;,再定义一个Direction = ParameterDirection.ReturnValue的参数接收这个返回值即可,不会干扰正常结果集的映射。
内容的提问来源于stack exchange,提问作者Nilesh
相关产品推荐
相关产品推荐

