当表值参数具有VARBINARY类型

时间:2016-03-03 20:35:20

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

我有一个C#Web应用程序,可以作为SQL Server的直通;请求详细说明SQL Server命令,我们解析请求,生成必要的.Net类型,然后使用它们来执行SqlCommands等。这样做的结果是C#Web应用程序需要非常灵活,并且实际上不能对请求应该做什么做出太多假设"应该"看起来像。

我最近解决了一个问题,当一个表值参数包含VARBINARY类型时,该问题导致抛出异常。在一般情况下,我们循环传入请求并构造具有适当数量的列和行的DataTable。未指定列的数据类型,数据值仅作为object插入。对于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来确切地知道发送到数据库服务器的内容。

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

1 个答案:

答案 0 :(得分: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();

        }