使用 ADO,使用 SqlDataAdapter.FillSchema 清除 @ReturnValues 和其他标记为输出的存储过程参数

Posted

技术标签:

【中文标题】使用 ADO,使用 SqlDataAdapter.FillSchema 清除 @ReturnValues 和其他标记为输出的存储过程参数【英文标题】:With ADO, using SqlDataAdapter.FillSchema clears @ReturnValues and other stored procedure parameters marked for output 【发布时间】:2020-06-26 15:43:14 【问题描述】:

[编辑 - 答案的解释在问题的底部,因为从接受的答案中,微妙之处并不完全显而易见。]

原问题:

在我做出进一步努力之前,我正在寻找一个快速的“是的,这种方法应该是可能的”答案。

场景:

我有一个运行查询的简单 SQL Server 存储过程。我还想 (1) 返回一个声明为 OUTPUTvarchar(100) 参数,以及 (2) 使用 @ReturnValue 返回一个进一步的(整数)值。 我使用 SqlDatAdapter.DataSet.Fill() 方法从 C# 调用此过程。 回到 C# 代码,我可以访问过程参数,但无法获取 @ReturnValue 或其他 OUTPUT 参数的值。

我在期待什么?

SqlDataAdapter 填充过程从选择中获取数据。这行得通,我可以在 C# 中访问这些数据。 标记为OUTPUT 的SQL Server 存储过程参数的值和自动@ReturnValue 参数的值将在C# 中可用。

我看到了什么?

数据按预期从查询返回。 SQL 命令参数存在于 C# 中,但它们的 .Value 属性为空。

我做了哪些研究?

大量搜索包含SqlDataAdapter@returnvalue 的点击。没有显着的阳性结果。 仔细阅读了与 SqlDataAdapter 相关的旧 SO 问题 - 似乎没有什么与我的问题重叠。 已发布此问题。

我需要询问 SO 社区这是否可行。那么 - 是否可以从与 SqlDatAdapter.DataSet.Fill() 一起使用的存储过程中收集 OUTPUT 参数值?

如果答案是肯定的,并且没有简单的示例,那么我会将我的代码缩减为一个有代表性的案例,并在此处发布(希望)用于社区调试。

编辑 感谢@NDJ 和提供的示例代码,我能够确认SqlDatAdapter.DataSet.Fill() 不会 干扰存储过程的返回值。然后我能够继续发现我的问题是由立即致电SqlDatAdapter.DataSet.FillScheam() 引起的。这会将数据表的元数据传递到一个方便的矩阵中,我们可以从 C# 访问该矩阵,这是我的用例的一部分。

但是,FillSchema() 会清除存储过程参数值。在我的代码中,我试图在调用 FillSchema 后收集存储过程的输出参数值,这就是我目睹的原因。我找不到关于这一点的任何文档。

如果你的代码看起来像这样,它就会工作。

    da.Fill(ds);

    var returnVal = returnParam.Value; // value as expected

    da.FillSchema(ds, SchemaType.Source);

如果您的代码看起来像这样,它将无法工作

    da.Fill(ds);

    da.FillSchema(ds, SchemaType.Source);

    var returnVal = returnParam.Value; // value reset by prev line !!!!

【问题讨论】:

我没有理由知道它不起作用。 @Crowcoder 感谢隧道中的那束积极的光线!你会碰巧有一个例子的链接吗? 我手边没有任何东西。但是,如果您的存储过程正在设置输出并返回一个值,那么该命令应该填充参数。您还必须确保您在适配器中查看正确的命令。 参数的方向默认为输入。您需要设置为输出:docs.microsoft.com/en-us/dotnet/api/… @jdweng - 谢谢,我相信我已经有了它,特别是因为在我在 C# 端设置输出参数的大小之前,我看到一个关于 size=0 无效的异常。 【参考方案1】:

是的,你可以。

Create PROCEDURE TestSP
    @test varchar(max) output
AS
BEGIN
    select @test = 'abc'
    select top 10 * from MyTable
    return 4

END




using (SqlConnection connection = new SqlConnection(conString))
        
            connection.Open();

            SqlCommand cmd = new SqlCommand("TestSP", connection);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter param = new SqlParameter("@test", SqlDbType.VarChar, -1);
            param.Direction = ParameterDirection.Output;

            var returnParam = cmd.Parameters.Add("@Return", SqlDbType.Int);
            returnParam.Direction = ParameterDirection.ReturnValue;

            cmd.Parameters.Add(param);

            SqlDataAdapter da = new SqlDataAdapter(cmd);
            DataSet ds = new DataSet();
            da.Fill(ds);

            var returnVal = returnParam.Value; //4
            var output = param.Value; // abc
            var data = ds.Tables[0].Rows[0][0];  //data from my table
        

【讨论】:

我已将您的答案标记为正确。在我的代码中,我立即调用 da.FillSchema(ds, SchemaType.Source);在调用 da.fill() 之后。实验表明,FillSchema() 调用清除了过程输出变量。谁知道。 —— 我当然没有:) 经过一段时间的思考,我直觉如下:从 C# 和 SQL Svr 之间的对话来看,SQL Svr 只是运行了一个 sproc,并将结果作为标准调用返回 'Execute '存储过程。聪明的地方在于,C# 必须要求 SQL Svr 为之前的输出提供模式。实现这一点的方法可能是调用 SQL Server 为此目的包含的一些特殊存储过程。因此,给定 C# 执行的存储过程与我原来的存储过程不同,第一个存储过程的输出变量已被清除,这似乎是明智的。

以上是关于使用 ADO,使用 SqlDataAdapter.FillSchema 清除 @ReturnValues 和其他标记为输出的存储过程参数的主要内容,如果未能解决你的问题,请参考以下文章

SqlDataAdapter.Fill 方法不会给出任何错误,但也不会返回任何数据以用于长时间运行的查询 ado.net core SQL Server

ado:SqlDataAdapter,dataset 与SqlDataReader的用法一

ado:SqlDataAdapter的两种不同写法,以及SqlCommand的两种不同写法

使用 ADO.NET 的批处理操作

在 ADO.NET 中使用或避免使用 CommandBuilder

SqlCommand和SqlDataAdapter有什么区别