4

I've written a bunch of generic C# helper functions as a base for my SQL queries. All null object parameters are converted to a DBNull.Value. But when the target column is a varbinary, it tells me that string type is not compatible with the binary one and I must convert.

Why do I need to SqlDbType a DBNull.Value in C#? And is there any generic way to go around this?

// works for nvarchar but throws for varbinary columns
// notice the lack of SqlDbType as I don't know it for a null object
var param1 = new SqlParameter();
param1.Name = "@Param1";
param1.Value = DBNull.Value;

It's a null... it needs no type. SQL server knows the null's type. In SQL you say [Column] IS NULL not [Column] IS TYPE NULL. So null is untyped. And the target column is resolved from the query not the SqlDbType.

I just find this very unintuitive (explicitly typing nulls). I'm wondering if I'm missing something...

Code is like this:

SqlCommand CreateCommand(string query, params object[] arguments) { ... }
// in here I walk the parameters and build the proper SqlParameter for them.
// this saves me from setting up the SqlParameters by hand.

PS: String = NVarChar and Binary means VarBinary. Seriously... PPS: Not interested in EF or anything of the like.

CodeAngry
  • 12,760
  • 3
  • 50
  • 57
  • 5
    Your question is not quite clear. How `string` to `binary` conversion related to `DBNull.Value`? Can you show some specific code sample? – Andrey Korneyev Mar 25 '15 at 11:45
  • Where did `string` enter the picture? If it's `DBNull` it isn't `string`. There's more to this story... – DonBoitnott Mar 25 '15 at 11:46
  • 3
    Also, can you show some example code that shows how you're doing this? Might provide some useful context – rory.ap Mar 25 '15 at 11:47
  • Well, you can read [the point of `DBNull.Value`](http://stackoverflow.com/questions/4488727/what-is-the-point-of-dbnull) – Soner Gönül Mar 25 '15 at 11:48
  • 3
    If you don't show your code we can't help to fix it. – Tim Schmelter Mar 25 '15 at 11:49
  • Are you **not** setting `DBType` of your `param1` explicitly? It is not clear from your code. – Andrey Korneyev Mar 25 '15 at 11:57
  • @AndyKorneyev I'm not. That's the problem. A null object is of unknown type. But it's a null. So DBNull.Value should be clear enough for C# + SQL Client. – CodeAngry Mar 25 '15 at 11:58
  • 2
    I believe the issue is that it needs to know the type of the parameter that is created. It's defaulting to `varchar` and even though you set it's value to `null` it just sees that you are comparing incompatible types. It's similar to creating a `string` variable in `C#` setting it to `null` and expecting to be able to compare it to a `int?` – juharr Mar 25 '15 at 11:59
  • @juharr This is not related to C#. That sqlparameter is just a means of delivering data to SQL server. But a NULL is always a NULL even in the database. **You say `[Column] IS NULL` not `[Column] IS VARBINARY(NULL)`.** – CodeAngry Mar 25 '15 at 12:01
  • 2
    @CodeAngry I was trying to give you a parallel example in C#. Your SQL will be `DECLARE @MyVar varchar` then `Set @MyVar = NULL` and then `[Column] = @MyVar`. It's not going to translate into `IS NULL`. – juharr Mar 25 '15 at 12:03

1 Answers1

6

If you are not setting DBType of your SqlParameter explicitly - it has default value SqlDBType.NVarchar (see MDSN for reference).

That's why you're getting exception - your parameter type is NVarchar but target column is VarBinary.

Note that you have to set DBType explicitly just because obviously ADO.NET can't deduce datatype from DBNull.Value.

null is not a type - it just absense of value, so if I'll give you null you can't deduce if it absence of varchar or, for example, absence of int.

UPDATE

I've made some experiments. Let's run simple code:

SqlCommand cmd = new SqlCommand("select * from sometable where somecolumn = @Param1");
cmd.Connection = _MyConnection;

var param = new SqlParameter();
param.ParameterName = "@Param1";
param.Value = DBNull.Value;
cmd.Parameters.Add(param);

cmd.Connection.Open();
try
{
    cmd.ExecuteNonQuery();
}
finally
{
    cmd.Connection.Close();
}

As we can see from SQL Profiler, this will be translated to actual SQL query as:

exec sp_executesql 
    N'select * from sometable where somecolumn = @Param1',
    N'@Param1 nvarchar(4000)',
    @Param1=NULL

So here we definitely will get exception if somecolumn datatype can't be implicitly converted from varchar just because @Param1 datatype is explicitly set to varchar.

At the first look - this can be good enough explanation, right?

But there is one issue confusing me. If we will execute not a straight query, but "dummy" stored procedure having varbinary as parameter, let's say:

create procedure [dbo].[usp_Test]
(
    @Param1 varbinary(max)
)
as
begin
    set nocount on

    select null
end

Now let's try to call it like:

SqlCommand cmd = new SqlCommand("dbo.usp_Test");
cmd.CommandType = CommandType.StoredProcedure;
... and so on (the rest of code dealing with parameter)

Now in SQL Profiler we'll see just this:

exec dbo.usp_Test @Param1=NULL

And this is pretty strange since it is absolutely legal call without explicit specification of parameter type. Actually, if we call this in SQL Management Studio - we will not get any exception.

This part of ADO.NET behaviour is quite strange to me. Probably SQL Profiler doesn't shows complete process (despite the fact I turned on all events tracking), I don't know.

Anyway - recommendation is still the same - always specify DBType of your SqlParameter explicitly.

Andrey Korneyev
  • 26,353
  • 15
  • 70
  • 71
  • In SQL, `datatype` is quite irrelevant. It all depends on the target column. The type is stored in the table definition not in what I send over. This is not SQLite where any column can have any type. – CodeAngry Mar 25 '15 at 12:05
  • But this isn't about what happens in SQL. It's about what is happening in .NET quite apart from the underlying DBMS. – rory.ap Mar 25 '15 at 12:08
  • @roryap Still `DBNull.Value` is a special value. It should be clear it's a null and need no type as it's resolved based on the target column. This is what I find silly. Why type a lack of something? I was hoping I'm missing something and this is not by design. – CodeAngry Mar 25 '15 at 12:10
  • 1
    @CodeAngry in fact, datatype is relevant in SQL. It looks "irrelevant" just because of implicit type conversions which are working good in most cases (for example, from `varchar` to `int` and so on) but not from `varchar` to `varbinary` – Andrey Korneyev Mar 25 '15 at 12:11
  • @AndyKorneyev You do understand we are talking about `DBNull.Value` not random valid objects? This is a very specific case... not `varchar` to `varbinary` conversion. Of course that's not gonna (supposed) to just work. But null is still null. – CodeAngry Mar 25 '15 at 12:14
  • 2
    @CodeAngry, SQL is a strongly typed language so all parameters must have a data type regardless of the value (including DBNull.Value). .NET must play by these rules when sending a parameter to the database even if it does it behind the scenes for you. SQL Server will also implicitly convert parameters to a different data type according to data type precedence rules when needed. – Dan Guzman Mar 25 '15 at 12:28
  • Well... I like the edits. Indeed it's weird as they could have just not required a type for null values and make things easier when writing generic helpers. – CodeAngry Mar 25 '15 at 13:55