调用存储过程必须设 CommandType = CommandType.StoredProcedure,否则报“找不到存储过程”;OUTPUT 参数和 RETURN 值需 ExecuteNonQuery() 后手动取值;异步调用时 SqlConnection 生命周期须覆盖参数读取;调试优先看 SqlException.Number。

用 SqlCommand 调用存储过程,别漏设 CommandType
直接执行存储过程最常用也最容易出错的方式,就是忘了把 SqlCommand.CommandType 从默认的 Text 改成 StoredProcedure。不改的话,SQL Server 会把你的存储过程名当普通 SQL 字符串去解析,报错 "Could not find stored procedure 'xxx'" 或直接语法错误。
实操建议:
-
cmd.CommandText只填存储过程名(如"usp_GetUserById"),不要加EXEC或括号 - 必须显式设置
cmd.CommandType = CommandType.StoredProcedure - 参数用
cmd.Parameters.Add()添加,别拼字符串——防注入、保类型准确
带输出参数和返回值的存储过程,ExecuteNonQuery 不够用
如果存储过程有 OUTPUT 参数或 RETURN 值,只调 ExecuteNonQuery 会拿不到结果。它只管执行,不读输出参数,也不捕获返回码。
实操建议:
- 无论增删改查,只要涉及
OUTPUT或RETURN,统一用ExecuteNonQuery()+ 手动取值 -
cmd.Parameters["@Result"].Value读OUTPUT参数(注意类型匹配) -
(int)cmd.Parameters["@ReturnCode"].Value读返回值(需提前加Direction = ParameterDirection.ReturnValue) - 别依赖
ExecuteScalar想“顺便”拿返回值——它只认第一列结果集,不是RETURN
异步调用存储过程时,await cmd.ExecuteNonQueryAsync() 是基础,但连接不能提前释放
用 async/await 调存储过程本身没问题,但常见错误是把 SqlConnection 包在 using 里,又在 await 后继续访问参数值。连接一释放,参数里的 Value 就可能变 null 或抛 InvalidOperationException。
实操建议:
- 确保
SqlConnection的生命周期覆盖整个异步操作 + 参数读取完成 - 要么把
using提到外层方法,要么用try/finally显式控制关闭时机 - 别在
await后再开新连接去读参数——输出参数值只在原命令上下文里有效 - 如果返回结果集,优先用
ExecuteReaderAsync()配合await foreach,别用DataTable.LoadAsync()(.NET 6+ 才支持)
调试存储过程执行失败,先看 SqlException.Number,不是只盯消息文本
报错信息里 “Timeout expired” 或 “Invalid object name” 看着像网络或权限问题,实际可能是存储过程里某句 SQL 报错被吞了。SQL Server 把内部错误号(如 547 外键冲突、2627 唯一约束)塞进 SqlException.Number,而 Message 常被截断或泛化。
实操建议:
- catch
SqlException后,优先打日志:e.Number、e.State、e.Procedure、e.LineNumber -
e.Number == 18456是登录失败,2812是找不到存储过程——这两类错常因环境配置差异导致(比如测试库没同步部署 SP) - 本地能跑、线上挂?检查目标库是否真的存在该 SP,且用户有
EXECUTE权限(不是只给db_datareader) - 别靠
TRY...CATCH在 SQL 里静默吞错——C# 层收不到原始错误号
执行存储过程看着简单,但输出参数生命周期、异步上下文绑定、错误号语义这些点,一不留神就卡半天。尤其跨环境部署后,权限和对象是否存在,比代码逻辑还容易漏。











