【问题标题】:Empty DataTable Causes Errors When Table-Valued Parameter Has VARBINARY Types当表值参数具有 VARBINARY 类型时,空 DataTable 会导致错误
【发布时间】:2016-03-03 20:35:19
【问题描述】:

我有一个 C# Web 应用程序,用作 SQL Server 的传递;请求详细的 SQL Server 命令进来,我们解析请求,生成必要的 .Net 类型,然后使用它们执行SqlCommands 等。这样做的结果是 C# Web 应用程序需要非常灵活,并且不能对请求“应该”是什么样子做出太多假设。

我最近解决了一个问题,当表值参数包含VARBINARY 类型时会引发异常。在一般情况下,我们循环传入的请求并构造一个具有适当列数和行数的DataTable。没有指定列的数据类型,数据值只是作为objects 插入的。对于VARBINARY 类型,这将导致以下错误:

不允许从数据类型 nvarchar(max) 到 varbinary(max) 的隐式转换。使用 CONVERT 函数运行此查询。

我找到了this StackOverflow post,并且能够解决问题。请看下面我的伪代码:

for (var colNdx = 0; colNdx < requestTvp.Columns.Count; ++colNdx)
{
    myDataTable.Columns.Add();

    if (requestTvp.Rows.Empty)
    {
        continue;
    }

    if (requestTvp.Rows[0].Columns[colNdx].DataType == Bytes)
    {
        myDataTable.Columns[colNdx].DataType = typeof(SqlBinary);
    }
}

据我所知,这很好用。当传入的 TVP 没有行时会出现问题,因为我没有要检查的数据。在这种情况下,我们只需构造一个具有正确列数的DataTable(这在有关 TVP 的请求元数据中指定),但这些列没有明确设置的数据类型。在这种情况下,DataTable 没有行。这会导致相同的异常:

不允许从数据类型 nvarchar(max) 到 varbinary(max) 的隐式转换。使用 CONVERT 函数运行此查询。

这是完整的堆栈跟踪:

System.Data.SqlClient.SqlException (0x80131904): Implicit conversion from data type nvarchar(max) to varbinary(max) is not allowed. Use the CONVERT function to run this query. The data for table-valued parameter "@tvp" doesn't conform to the table type of the parameter.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)

老实说,我根本不明白为什么会出现问题...如果DataTable 中没有数据,甚至是什么导致了无效的隐式转换?

让事情更令人沮丧的是,这个错误甚至在请求到达我的数据库之前就发生了,所以我无法使用 SQL Server Profiler 来确定发送到数据库服务器的确切内容。

这是我的问题:当相应的用户定义表类型具有 VARBINARY 字段时,如何正确地将空的 DataTable 作为表值参数传递给存储过程?在构造DataTable 时,关于表值参数的唯一信息是用户定义表类型的名称、它包含的列数、存储过程中的参数名称和值包含在请求 TVP 中,在这种情况下它本质上是一个空集。

【问题讨论】:

  • 此列Columns[colNdx] 中的数据是什么,例如是时间戳...?如果是这样,则为myDataTable.Columns[colNdx].DataType = 赋值
  • @MethodMan - 在问题情况下,我们甚至没有落入该代码块,因为我们没有要考虑的行。我意识到这在我最初发布的伪代码中并没有立即显现出来,因此我对其进行了修改以使其更具说明性。
  • 这听起来像是一个愚蠢的问题,但是,如果您没有数据要传递,为什么还要调用存储的过程呢?如果可能,请在没有数据时跳过通话。
  • @Kevin - 不是一个愚蠢的问题,但也不是一个可接受的解决方案(-:我不能假设仅仅因为存储过程是用一个空的 TVP 参数调用的,它就不会- op;它可能仍然会产生副作用甚至返回数据。
  • 我认为您应该检查以下if(datatable.Rows.Count &gt; 0)

标签: c# sql-server datatable varbinary table-valued-parameters


【解决方案1】:

这是我想出的解决方法。如果数据表为空,就绕过表类型的SqlCommand对象声明,自己动手:

      using(SqlConnection c = new SqlConnection("<connectionString>"))
        {
            c.Open();

            DataTable emptyTable = new DataTable();

            emptyTable.Columns.Add("c1", typeof(int));
            emptyTable.Columns.Add("c2", typeof(string));

            DataRow row = emptyTable.NewRow();

            row["c1"] = 99;
            row["c2"] = new String('X', Int16.MaxValue);

           // Uncomment to make table non empty
           // emptyTable.Rows.Add(row);

            SqlCommand cmd = c.CreateCommand();

            if (emptyTable.Rows.Count > 0)
            {
                cmd.CommandText = "dbo.BOB";
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                SqlParameter p = cmd.Parameters.AddWithValue("@TP", emptyTable);

                p.SqlDbType = SqlDbType.Structured;
                p.TypeName = "dbo.KrjVarBin";
            }

            else
            {
                cmd.CommandType = CommandType.Text;
                cmd.CommandText = "declare @empty as dbo.KrjVarBin; execute dbo.bob @empty";
            }

            cmd.ExecuteNonQuery();

        }

【讨论】:

  • 感谢您提供解决方法。不幸的是,在这种情况下,我需要一个答案,而不是解决方法,所以我不接受这个。
  • 当我追踪它时,元数据验证正在数据表列与表类型上进行。我认为这样它就会知道如何构造插入语句来填充表值变量。似乎没有任何方法可以关闭此检查。所以即使表是空的,你还是得让DataTable列中的数据类型与目标表类型列兼容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-02
  • 2023-01-25
相关资源
最近更新 更多