存储过程输入参数需在CREATE PROCEDURE中显式声明,类型与调用端严格对齐;默认值仅省略时生效,不可兜底业务逻辑;NULL参数需用IS NULL或ISNULL处理;返回结果集必须用SELECT,OUTPUT仅支持标量值;C#调用须设CommandType.StoredProcedure、参数名一致、DateTime优先用DateTime2、NULL传DBNull.Value;OUTPUT参数值须在读完所有结果集后才可用。

存储过程怎么声明和用好输入参数
输入参数必须在 CREATE PROCEDURE 语句里显式声明,类型要和调用端对齐,否则会隐式转换失败或截断。比如传 DATETIME 给 DATE 参数,SQL Server 可能丢掉时间部分;传长字符串给 VARCHAR(10) 就直接截断,还不报错。
常见写法:@id INT、@name NVARCHAR(50)、@start_date DATE = NULL(带默认值可选)。注意:默认值只在调用时省略该参数才生效,不能靠它兜底业务逻辑。
- 参数名大小写不敏感,但建议和定义保持一致,避免在区分大小写的数据库排序规则下出问题
- 不要在参数名前加
sp_前缀——SQL Server 会先查系统存储过程,多一次查找开销 - 如果参数可能为
NULL,记得在 WHERE 条件里用IS NULL或ISNULL(@p, col) = col,别直接写= @p
返回单行/多行结果该用 SELECT 还是 OUTPUT
要返回表结构数据(哪怕只有一行),必须用 SELECT;OUTPUT 参数只能返回标量值(单个数字、字符串、日期等),不能返回列集合。
SELECT 最直接:过程里写一句 SELECT Name, Email FROM Users WHERE Id = @id,调用方就能拿到结果集。但要注意:
- 确保逻辑上只产生一个结果集——前面有其他
SELECT或PRINT,客户端就得处理多结果集,容易漏读 - 如果必须保证单行,加
TOP 1或用IF NOT EXISTS配合RAISERROR控制流程,别依赖应用层去判空 - 字段别名尽量明确,比如
SELECT @id AS UserId,避免调用方靠序号取值(reader[0])导致维护脆弱
C# 调用时怎么把输入参数传进去并拿到结果
核心是两件事:设对 CommandType,绑对参数方向。用 CommandType.Text 拼 "EXEC proc @p=1" 看似能跑,但输出参数拿不到、类型推断错、还有 SQL 注入风险。
正确做法:
- 设置
command.CommandType = CommandType.StoredProcedure - 参数名必须和存储过程中定义的完全一致(如
@user_id就不能传成userId) - 输入参数不用设
Direction(默认Input),但DateTime类型优先用SqlDbType.DateTime2避免精度丢失 - 传
NULL时,必须赋DBNull.Value,不能传 C# 的null
示例关键行:
cmd.Parameters.Add("@id", SqlDbType.Int).Value = 123;<br>using var reader = cmd.ExecuteReader();<br>if (reader.Read()) { string name = reader.GetString("Name"); }
为什么 EXEC 后读不到 OUTPUT 参数的值
因为 OUTPUT 参数的值,是在所有结果集被消费完之后才写入参数对象的。如果你用 ExecuteReader() 只读了第一个结果集就关掉 reader,那 command.Parameters["@count"].Value 还是初始值或 NULL。
典型踩坑场景:
- 存储过程里先
SELECT * FROM Orders,再SET @total = @@ROWCOUNT,然后SELECT @total AS Total—— 这其实是两个结果集 - ADO.NET 中必须循环调用
reader.NextResult()直到返回false,才能安全读Parameters - SSMS 里看不出来,是因为它自动帮你捞完了;程序里不手动遍历,就永远卡在第一个结果集
最稳妥的做法:如果只需要结果集,就别混用 OUTPUT;如果必须两者都要,执行完后统一用 ExecuteNonQuery() + 从 Parameters 取值,避开 reader 生命周期干扰。

















