执行存储过程时如何获取输出参数以及table

How to get output parameter and also a table when executing a stored procedure

场景

情况

我有一个 return 是一个 table 的 USP,它至少有 10 万行乘以 20 个字段。我需要添加一个输出参数,以便获得由 USP 本身创建的 ID。所以,情况是我需要 return 一个 table 和一个 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# 级别添加输出。它 return 在行中:

字符串 ProcMonitorId = outPutParameter.Value.ToString();

it returns NullReferenceException 因为 Value 为 null(不可能),当然不能转换为 String。我会通过添加 ? 来解决这种情况,但如果这种情况真的发生了,我需要以任何方式将其捕获为错误。主要思想是Value不能为null。

因为我没有任何 ORM 映射,(而且我的专长不是 ADO.NET,而是 Entity Framework)我不明白为什么是 null(不,在 SQL层,总是return一个值)

问题

如何解决此错误或如何 return BIGINT 参数和 table 结果?

解决方法

因为我第一眼看到我必须快速解决它,所以我做了一个:

SELECT 1 as type, @procID as procid, null as data1, null as data2
UNION ALL
SELECT 2 as type, null as procid, data1, data2

为了在一个 table 上模拟“header”和“数据”行。 但我不喜欢这种解决方案,而且不是很优雅和灵活。我每次都必须解析 header。

提前致谢,请评论任何内容、提示、帮助、解决方法,如果需要更多信息,我很乐意更新我的答案。

我也可以将我的框架制作成 .NET Core 或更改为 Entity Framework。我无法更改的是我的 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);

您应该使用方向 属性 设置为 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     
}

参数值只有在您使用结果集后才可用,例如

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;
}