执行存储过程时如何获取输出参数以及table
How to get output parameter and also a table when executing a stored procedure
场景
- 我正在使用 SQL Server 2017(无法更改)
- 我在 C# 控制台和 .NET Framework 4.5 中使用 Visual Studio 2019(可能会更改)
- 我正在使用 ADO.NET,因为几年前我们无法使用 Entity Framework,因为系统被设计为使用 return 至少 100k 的存储过程行(可能更改)
情况
我有一个 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;
}
场景
- 我正在使用 SQL Server 2017(无法更改)
- 我在 C# 控制台和 .NET Framework 4.5 中使用 Visual Studio 2019(可能会更改)
- 我正在使用 ADO.NET,因为几年前我们无法使用 Entity Framework,因为系统被设计为使用 return 至少 100k 的存储过程行(可能更改)
情况
我有一个 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;
}