我有一个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中包含的值,在这种情况下基本上是一个空集。
答案 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();
}