执行存储过程时如何获取输出参数和表
Posted
技术标签:
【中文标题】执行存储过程时如何获取输出参数和表【英文标题】:How to get output parameter and also a table when executing a stored procedure 【发布时间】:2022-01-10 00:59:51 【问题描述】:场景
我正在使用 SQL Server 2017(无法更改) 我在 C# 控制台和 .NET Framework 4.5 中使用 Visual Studio 2019(可能会更改) 我使用 ADO.NET 是因为几年前我们还不能使用 Entity Framework,因为系统是用来处理返回至少 100k 行的存储过程(可能会更改)情况
我有一个 USP,它返回一个至少 100k 行乘 20 个字段的表。我需要添加一个输出参数才能获得由 USP 本身创建的 ID。所以,情况是我需要返回一个表和一个 ID(称为ProcMonitorId
)。我不知道这是否可能(请参阅解决方法部分)
到目前为止,SQL 级别似乎还不错:
CREATE PROCEDURE [myschema].[mystore]
@ProcMonitorId BIGINT OUTPUT
AS
BEGIN
BEGIN TRANSACTION
(...)
SELECT fields FROM myTable
SELECT @ProcMonitorId = @internalVariable
SQL 执行:
在存储库层(仅相关行,有人对示例的健康状况感到惊讶):
var command = new SqlCommand("myStoreProcedure", mycon);
command.CommandType = CommandType.StoredProcedure;
SqlParameter outPutParameter = new SqlParameter();
outPutParameter.ParameterName = "@ProcMonitorId";
outPutParameter.SqlDbType = System.Data.SqlDbType.BigInt;
outPutParameter.Direction = System.Data.ParameterDirection.Output;
command.Parameters.Add(outPutParameter);
// Open connection etc-etc that works
SqlDataAdapter da = new SqlDataAdapter(command);
DataTable dt = new DataTable();
string ProcMonitorId = outPutParameter.Value.ToString();
da.Fill(dt);
在添加 C# 级别的输出之前,一切正常。它在行中返回:
字符串 ProcMonitorId = outPutParameter.Value.ToString();
它返回NullReferenceException
,因为Value
为空(不可能),当然,不能转换为字符串。我会通过添加?
来解决这种情况,但如果这种情况真的发生,我需要以任何方式将其作为错误捕获。主要思想是Value不能为null。
由于我没有任何 ORM 映射,(而且我的专长不是 ADO.NET 而是实体框架)我无法理解为什么为 null(不,在 SQL 层不为 null,总是返回一个值)
问题
如何解决此错误或如何返回 BIGINT
参数和表格结果?
解决方法
乍一看我必须快速解决它,我做了一个:
SELECT 1 as type, @procID as procid, null as data1, null as data2
UNION ALL
SELECT 2 as type, null as procid, data1, data2
为了在一个表上模拟“标题”和“数据”行。 但我不喜欢这个解决方案,也不是很优雅和灵活。我每次都要解析头部。
提前致谢,请评论任何内容、提示、帮助、解决方法,如果需要更多信息,我将很乐意更新我的答案。
我还可以将我的框架制作为 .NET Core 或更改为实体框架。我无法更改的是我的 SQL 版本
更新 #2 SQL 没有变化 - 仍然作为屏幕截图工作 在 C# 中 - 永远挂在外面
SqlConnection connection = new SqlConnection(ConfigurationManager.AppSettings["DbConnection"]);
connection.Open();
var command = new SqlCommand("myUSP", connection);
command.CommandType = CommandType.StoredProcedure;
command.CommandTimeout = Convert.ToInt16(ConfigurationManager.AppSettings["DataBaseTimeOut"]);
if (connection.State != ConnectionState.Open)
connection.Open();
SqlParameter r = command.Parameters.Add("@ProcMonitorId", SqlDbType.BigInt);
r.Direction = ParameterDirection.Output;
DataTable dt = new DataTable();
using (var rdr = command.ExecuteReader())
dt.Load(rdr);
long id = (long)r.Value;
SqlDataAdapter da = new SqlDataAdapter(command);
da.Fill(dt);
【问题讨论】:
不需要 DataAdapter 填充,因为表已经在 dt.Load 中加载(当然反之亦然) @Steve 你是对的,我混淆了解决方案 您的程序有BEGIN TRANSACTION
,但没有COMMIT
。理想情况下,您需要使用 while (rdr.NextResult)
来确保执行所有 SQL,但在这种情况下,请尝试将 SELECT @ProcMonitorId
放在首位
@Charlieface 提交包含在 (...)
【参考方案1】:
在您使用结果集之前,参数值将不可用,例如
var cmd0 = new SqlCommand("create or alter procedure pFoo @id int output as begin select * from sys.objects; set @id = 12; end", con);
cmd0.ExecuteNonQuery();
var cmd = new SqlCommand("pFoo", con);
cmd.CommandType = CommandType.StoredProcedure;
var p1 = cmd.Parameters.Add("@id", SqlDbType.Int);
p1.Direction = ParameterDirection.Output;
var dt = new DataTable();
using (var rdr = cmd.ExecuteReader())
dt.Load(rdr);
var id = (int)p1.Value;
【讨论】:
谢谢!让我们试试这个。对不起我的无知:我应该对我原来的 USP 做一些改变吗?我应该将@id 设置为 OUTPUT 吗? 是的。您应该使用 OUTPUT 参数。 现在它再次适用于 SQL(与屏幕截图中相同)但在 C# 中超时 超时是客户端设置。 SqlCommand.CommandTimeout。 docs.microsoft.com/en-us/dotnet/api/… 是的,是的,我用了 2 个小时,但即将出去玩。但我认为是其他问题,而不是 ADO【参考方案2】:您应该使用将 Direction 属性设置为 ReturnValue 的参数,并在 sp 中声明一个内部变量并将其设置为您想要的值。 然后在离开 StoredProcedure 之前调用 RETURN 语句。
作为示例,请参阅此 SP:
ALTER PROCEDURE [GetTimeZoneGMT]
@TimeZone NVARCHAR(128)
AS
BEGIN
DECLARE @timeZoneNumber as INT = -20;
IF @TimeZone ='Pacific/Midway'
SET @timeZoneNumber = -11
ELSE IF @TimeZone ='Pacific/Niue'
SET @timeZoneNumber = -11
ELSE IF @TimeZone ='Pacific/Pago_Pago'
SET @timeZoneNumber = -11
SELECT 1 -- or whatever you need to have as result set
RETURN @timeZoneNumber;
END
存储过程以(虚假的)SELECT 语句结束,但也有一个 RETURN 语句,其参数设置在 SP 逻辑内。
现在从 C# 端你可以这样调用它(LinqPad 示例)
using (var connection = new SqlConnection("Data Source=(LOCAL);Initial Catalog=LinqPADTest;Integrated Security=True;"))
connection.Open();
SqlCommand cmd = new SqlCommand("GetTimeZoneGMT", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@TimeZone", SqlDbType.NVarChar).Value = "Asia/Kuala_Lumpur";
SqlParameter r = cmd.Parameters.Add("@p2", SqlDbType.BigInt);
r.Direction = ParameterDirection.ReturnValue;
DataTable dt = new DataTable();
dt.Load(cmd.ExecuteReader());
r.Value.Dump(); // Prints -20
dt.Dump(); // Prints a row with a single column with 1 as value
【讨论】:
非常感谢!嗯,让我试试!当然我没有使用 RETURN。 我试过了,但现在它永远等待。在 SQL 中不返回参数。只有第一个选择。我只是更新我的问题 RETURN 值不适用于向客户端发送数据。它们用于向其他 TSQL 代码报告存储过程成功或失败。 @DavidBrowne-Microsoft 不想争论,但是为什么我们有一个方向 = ReturnValue 的 SqlParameter。对我来说,这似乎只是一条未写成且未在任何地方强制执行的规则。据我所知,我在需要时使用它并且没有副作用 是的,这只是一个最佳实践。以上是关于执行存储过程时如何获取输出参数和表的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 simpleJdbcCall 在存储过程中获取 XML 输出参数