问题
I have a C# web application that serves as a passthrough to SQL Server; requests that detail SQL Server commands come in, we parse the request, generate the necessary .Net types and then use them to execute SqlCommands
and such. The upshot of that is that the C# web application needs to be very flexible and really can't make too many assumptions about what a request "should" look like.
I recently solved a problem that was causing exceptions to be thrown when a table-valued parameter contained VARBINARY
types. In the general case, we loop over the incoming request and construct a DataTable
with the appropriate number of columns and rows. The data type of the columns weren't specified, and the data values were just inserted as object
s. For VARBINARY
types, this would result in the following error:
Implicit conversion from data type nvarchar(max) to varbinary(max) is not allowed. Use the CONVERT function to run this query.
I found this StackOverflow post, and was able to solve the problem. Please see my pseudo-code below:
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);
}
}
From what I can tell, this is working great. The problem occurs when the incoming TVP has no rows since I have no data to check against. In this case, we just construct a DataTable
with the correct number of columns (this is specified in the request metadata about the TVP), but these columns have no explicitly set data type. The DataTable
has no rows in this case. This results in the same exception:
Implicit conversion from data type nvarchar(max) to varbinary(max) is not allowed. Use the CONVERT function to run this query.
Here is the full stack trace:
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)
Honestly, I don't understand why the problem is occurring at all...if there's no data in the DataTable
what is even causing the invalid implicit conversion?
What makes things even more frustrating, is that this error occurs before the request even gets to my database, so I'm not able to use SQL Server Profiler to figure out exactly what is being sent to the database server.
So here's my question: how do I correctly pass an empty DataTable
as table-valued parameter to a stored procedure when the corresponding User-Defined Table Type has a VARBINARY
field? The only information I have about the table-valued parameter when I'm constructing the DataTable
is the name of the User-Defined Table Type, the number of columns it contains, the name of the parameter in the stored procedure and the values contained in the request TVP, which in this case is essentially an empty set.
回答1:
Here is the work around I have come up with. If the data table is empty, just bypass the SqlCommand objects declaration of the table type and do it yourself:
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();
}
来源:https://stackoverflow.com/questions/35782496/empty-datatable-causes-errors-when-table-valued-parameter-has-varbinary-types