2

Recently, we ran into the following issue:

In a nutshell: myCommand.Parameters.AddWithValue("@SomeParameter", DBNull.Value); apparently "types" the DBNull parameter as an nvarchar, which can be implicitly converted to almost all other types, but, unfortunately, not varbinary, yielding the following error:

Implicit conversion from data type nvarchar to varbinary(max) is not allowed. Use the CONVERT function to run this query.

Unfortunately, the solutions suggested in the linked question do not apply, since we use AddWithValue deep inside a data access library which is designed to infer the data type from the parameter type and does not support adding the "real" SQL Server type.

I "fixed" this issue by explicitly typing DBNull parameters as ints instead:

void MyImprovedAddWithValue(SqlCommand cmd, string key, object value)
{
    if (value == DBNull.Value) 
    {
        cmd.Parameters.Add(key, SqlDbType.Int).Value = DBNull.Value;
    }
    else
    {
        cmd.Parameters.AddWithValue(key, value);
    }
}

This seems to work. Apparently, NULL typed as int can be implicitly converted to any other type supported by SQL Server.

Ignoring the fact that AddWithValue has some well-known shortcomings (which we are perfectly aware of), are there any problems to be expected by using this approach? Did the designers of SqlParameterCollection.AddWithValue have a good reason not to do this "by default" which I am overlooking?

Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • AddWithValue works good with DBNULL, you will have problems when value == null – Mikhail Lobanov Dec 21 '16 at 19:08
  • 2
    I suggest you avoid AddWithValue entirely and explicitly specify the proper type of the parameter. Not only will this avoid surprises, it can improve performance, in some cases considerably. – Dan Guzman Dec 21 '16 at 19:20
  • That's all fine and good when you're constructing parameters in top-level code, but doesn't make a lot of sense for a framework or API that constructs SqlParameters deep within layers of extraction. – Chris Berger Dec 21 '16 at 19:25
  • @MikhailLobanov: It doesn't when the target data type is varbinary, see the linked question. – Heinzi Dec 21 '16 at 19:27
  • @DanGuzman: I agree, and that's what we'll do the next time we do a complete rewrite of our data access layer. The lesson has been learned. At the moment, we have tons of legacy code which does *not* supply the proper type of the parameter and which is not scheduled for a rewrite anytime soon. – Heinzi Dec 21 '16 at 19:29

2 Answers2

1

AddWithValue has more problems. There is the issue of query plan cache pollution caused by the fact that it creates a parameter of type NVARCHAR(X) where X is the exact length of the value. As NVARCHAR(73) is a different type from NVARCHAR(74), the SQL Server query plan cache gets polluted with many identical plans that differ only on the parameter type. Another issue is when a column of type VARCHAR is involved in a comparison and the parameter of type NVARCHAR causes type conversion than disqualifies use of an index on said column.

There are so many reasons against AddWithValue that I never use it, never recommend it, and always add typed wrappers around it. I suggest you do the same.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
1

No, this is not always safe, per the conversion chart (under "implicit conversions"). INT can be implicitly converted to a lot, but not to DATE, TIME, DATETIMEOFFSET, DATETIME2, UNIQUEIDENTIFIER, IMAGE, NTEXT, TEXT, XML, CLR UDTs and HIERARCHYID. Some of these types are more common than others, but the incompatibility with DATETIME2 and UNIQUEIDENTIFIER would be easy to encounter in practice.

There are no T-SQL types that have implicit conversions to every other type, so there really is no good way of doing this if you don't know the target type. Of all the types, the character types CHAR and VARCHAR have the most implicit conversions, lacking only BINARY, VARBINARY (your case) and the far less used TIMESTAMP. NCHAR and NVARCHAR are close behind, additionally lacking the deprecated IMAGE type. With all this in mind, NVARCHAR is clearly the best choice for a default, since it can represent all strings and fails only on implicit conversion to the BINARY types, which are less common than the types INT does not convert to (even for NULL).

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • Ouch, the missing `uniqueidentifier` conversion would have broken a lot of code. Thanks! It's not what I hoped for, but it answers the question completely. Apparently, replacing the parameter with a literal `NULL` is the only way around this... – Heinzi Dec 22 '16 at 12:12