执行存储过程时如何获取输出参数和表

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 输出参数

如何获取作为数组工作的存储过程输出参数?

SQL Server 如何执行 带参数的 存储过程

oracle中怎么执行带有输出参数的存储过程,在程序中我知道怎么调用,

如何从存储过程中获取输出参数结果和查询结果?