EF Core 调用存储过程需严格对齐 API 选择、参数配置与返回值处理:查数据首选 FromSqlInterpolated(实体字段须与 SELECT 严格一致);增删改用 ExecuteSqlCommand(仅返影响行数,不刷新实体);单值用 Database.SqlQuery(限单列);复杂场景(输出参数、多结果集)必须用 DbCommand 手动控制,并加 SET NOCOUNT ON 防错位。

EF Core 调用存储过程不是“能不能”的问题,而是“用哪个 API、怎么配参数、怎么接返回值”三个动作必须对齐——错一个,就查不到数据、拿不到输出参数、或直接抛 InvalidOperationException。
查数据:FromSqlInterpolated 是默认首选,但 DTO 必须显式配置
只要存储过程返回结果集(比如 SELECT id, name FROM Customers),就该用 FromSqlInterpolated,它自动参数化,防 SQL 注入。但注意:EF Core 不接受“字段少几个”或“多了个计算列”的松散匹配。
- 实体类字段名、类型、数量必须和 SELECT 列严格一致;不一致就新建一个轻量
CustomerSummary类 - 在
OnModelCreating中必须加modelBuilder.Entity().HasNoKey().ToSqlQuery("EXEC GetCustomerSummary") - 不能用
FromSqlRaw拼接用户输入,比如FromSqlRaw($"EXEC GetCustomer @name = '{name}'")—— 这是硬编码,等同于裸写 SQL - 多结果集(如 SP 里两个
SELECT)会被忽略第二个起,EF Core 原生不支持
执行增删改:ExecuteSqlCommand 只返回影响行数,不自动刷新实体
ExecuteSqlCommand 适合没有结果集、只关心“改了几行”的场景,比如 UPDATE Order SET Status = @status WHERE Id = @id。它不映射任何实体,也不触发变更跟踪。
- 参数必须用
SqlParameter显式构造,命名要和 SP 定义一致(@Id≠@id) - 返回值是
int:成功时为影响行数,-1 表示执行失败(如语法错误、权限不足) - 调用后如果想看到最新状态,得手动
_context.Entry(entity).Reload()或重新Find/FirstOrDefault - 不支持输出参数(
OUTPUT子句或ParameterDirection.Output),要拿输出值必须换DbCommand
取单值或标量:Database.SqlQuery 要求结果严格单列
当存储过程只返回一个值(如 SELECT COUNT(*) 或 SELECT MAX(Id)),用 Database.SqlQuery 最干净。它绕过 EF 的实体生命周期,纯读取。
- 必须确保 SP 最终只返回一列(哪怕多行,也只取第一列);若返回两列,会报
Invalid operation exception: The data reader has more than one field. - 不能用在
DbSet上,只能走_context.Database.SqlQuery - 不支持异步变体(
SqlQueryAsync),EF Core 8 仍无此 API,需自行包装Task.Run或改用DbCommand - 别和
FromSqlInterpolated混用同一个实体类——前者不走变更跟踪,后者会进上下文,容易引发状态冲突
带输出参数或多结果集:必须用 DbCommand 手动控制
原生 API 在输出参数、RETURN 值、多个 SELECT 结果之间切换时完全失效。这时候就得退到 ADO.NET 层,用 DbCommand 自己读。
- 从
_context.Database.GetDbConnection()获取连接,确保已打开 - 用
command.Parameters.Add(new SqlParameter("@out", SqlDbType.Int) { Direction = ParameterDirection.Output })显式设方向 - 执行后调
reader.NextResult()跳到第二个结果集,再while (reader.Read())读数据 - 别忘了
using包裹 command 和 reader,否则连接泄漏风险高
最常被跳过的细节是:存储过程里漏写 SET NOCOUNT ON,会导致 EF Core 把额外的“XX 行受影响”消息当成第一个结果集,然后所有后续读取全错位——这个坑不看 Profiler 几乎没法定位。











