views:

702

answers:

10

I'm writing a C# routine to call a stored proc. In the parameter list I'm passing in, it is possible that one of the values can legally be null. So I thought I'd use a line like this:

cmd.Parameters.Add(new SqlParameter("@theParam", theParam ?? DBNull.Value));

Unfortunately, this returns the following error:

CS0019: Operator '??' cannot be applied to operands of type 'string' and 'System.DBNull'

Now, this seems clear enough, but I don't understand the rationale behind it. Why would this not work? (And often, when I don't understand why something isn't working, it's not that it can't work...it's that I'm doing it wrong.)

Do I really have to stretch this out into a longer if-then statement?

EDIT: (As an aside, to those suggesting to just use "null" as is, it doesn't work. I originally figured null would auto-translated into DBNull too, but it apparently does not. (Who knew?))

A: 

I'm pretty sure that just passing a null to the SqlParameter constructor results in it being sent as a DBNull.Value... I may be mistaken, since I use the EnterpriseLibraries for DB access, but I'm quite sure that sending a null is fine there.

Erich
Yes, you're wrong, -1. Trying to just pass null will result in SQL server to give a "the parameter ... does not exist" error.
erikkallen
Erich, I would've assume you're right on this one as I've observed the behavior you describe many times in VB.NET. Aside from the fact that this is C#, I don't know what other circumstances are different that allows it to work for me and not erikkallen or Beska.
Steve Wortham
Ah, I usually use the SqlHelper class and I just found this line inside: If (p.Direction = ParameterDirection.InputOutput OrElse p.Direction = ParameterDirection.Input) AndAlso p.Value Is Nothing Then p.Value = DBNull.Value End If... That's why it works.
Steve Wortham
A: 

Not sure the specific answer to your question, but how about this?

string.IsNullOrEmpty(theParam) ? DBNull.Value : theParam

or if blank is ok

(theParam == null) ? DBNull.Value : theParam
n8wrl
Nope- ternary requires the types to match as well.
Joel Coehoorn
D'oh! That'll teach me to post-before-test!
n8wrl
+4  A: 
new SqlParameter("@theParam", (object)theParam ?? DBNull.Value)
ifwdev
You don't even need to cast both of them to null. I usually do new SqlParameter("@theParam", (object)theParam ?? DBNull.Value)
erikkallen
+2  A: 

The ?? operator returns the left-hand operand if it is not null, or else it returns the right operand. But in your case they are different types, so it doesn't work.

Yuriy Faktorovich
(inconsequential edit made so that it would let me vote this answer up.)
Beska
A: 

cmd.Parameters.Add(new SqlParameter("@theParam", (theParam == null) ? DBNull.Value : theParam));

D B
+1  A: 

The reason you can't use the null coalesce operator is that it has to return one type and you are providing more than one type. theParam is a string. DbNull.Value is a reference to a static instance of type System.DbNull. This is what its implementation looks like;

public static readonly DBNull Value = new DBNull(); 
//the instantiation is actually in the 
//static constructor but that isn't important for this example

So if you were to have a NullCoalesce method, what would its return type be? It can't be both System.String and System.DbNull, it has to be one or the other, or a common parent type.

So that leads to this type of code;

cmd.Parameters.Add(
    new SqlParameter("@theParam", (object)theParam ?? (object)DBNull.Value)
);
Bob
+8  A: 

Not like that, no. The types have to match. The same is true for the ternary.

Now, by "match", I don't mean they have to be the same. But they do have to be assignment compatible. Basically: in the same inheritance tree.

One way to get around this is to cast your string to object:

var result = (object)stringVar ?? DBNull.Value;

But I don't like this, because it means you're relying more on the SqlParameter constructor to get your types right. Instead, I like to do it like this:

cmd.Parameters.Add("@theParam", SqlDbTypes.VarChar, 50).Value = theParam;
// ... assign other parameters as well, don't worry about nulls yet

// all parameters assigned: check for any nulls
foreach (var p in cmd.Parameters) 
{ 
    if (p.Value == null) p.Value = DBNull.Value; 
}

Note also that I explicitly declared the parameter type.

Joel Coehoorn
For info, Commands created by a CommandBuilder have a set of parameters built for you. However, the same issue arises in that you cannot just expect to pass null to one of the parameters in the generated ParameterCollection. you still have to do as described above.
rohancragg
Nice simple solution:-)
IrishChieftain
You could also create an extension method to do this.
John Cromartie
@John No, you can't, because that extension method would still return values of different types. You could encapsulate the Execute__() methods, but that's not really as helpful.
Joel Coehoorn
@Joel I mean the bit in the `foreach` could be an extension method of SqlCommand.
John Cromartie
+2  A: 

The Null Coalesce operator only with with data of the same type. You cannot send NULL to the SqlParamater as this will make Sql Server says that you didn't specify the parameter.

You can use

new SqlParameter("@theParam", (object)theParam ?? (object)DBNull.Value)

Or you could create a function that return DBNull when null is found, like

public static object GetDataValue(object o)
{
    if (o == null || String.Empty.Equals(o))
        return DBNull.Value;
    else
        return o;
}

And then call

new SqlParameter("@theParam", GetDataValue(theParam))
Pierre-Alain Vigeant
yeah, the GetDataValue method is how i've handled this in the past. I really wish there were a built-in behavior for this. Like a property you could specify on the ADO.NET classes, that would substitute DBNULL.Value (or something you specify) when it gets a null value.
LoveMeSomeCode
Yeah, me too I'd like to be able to specify null and let it map to DBNull automatically.
Pierre-Alain Vigeant
@LoveMe: Count me in for this should be handled by default!
Joel Coehoorn
A: 

Use this syntax:

(theParam as object) ?? (DBNull.Value as object)

In this case both parts of operator ?? are of the same type.

Vitaliy Liptchinsky
1) You only need to cast one side, both are overkill; 2) Using `as` unless you proceed to check the return value of the cast (i.e. as typeswitch) is a bad idea - a normal cast communicates the intent clearer.
Pavel Minaev
A: 

In your stored proc when you declare the incoming variable, have it set the var equal to null and then do not pass it in from your csharp code, it will then pick up the default value from sql

@theParam as varchar(50) = null

and then in your csharp

if (theParam != null)
    cmd.Parameters.Add(new SqlParameter("@theParam", theParam));

This is how I usually pass option and/or defaulted values to my stored procs

Anthony Shaw
I think you meant "!=", fixed you sample.
Joel Coehoorn
yes, thank you. I noticed it while I was trying to get the code block to work and forgot to go back and edit.
Anthony Shaw