views:

661

answers:

5

I am currently using a sql data reader (in vb.net) to extract an article object via a stored proc from a SQL Server 2008 database. Part of this object includes the two properties shown below:

theArticle.Truthfulness = ((myReader.GetInt32(myReader.GetOrdinal("Truthfulness"))))
theArticle.Relevance = ((myReader.GetInt32(myReader.GetOrdinal("Relevance"))))

My problem is that the Truthfulness and Relevance may return a null value and this is causing the function to fall over.

I think I understand why. I am asking for an integer value (getin32) and because null is returned it fails.

How do I accommodate the null value from the database so it does not fall over?

+4  A: 

You can check whether or not a given ordinal position is null using .IsDBNull() and then do something - e.g. set your value to -1 or something:

int myOrdinal = myReader.GetOrdinal("Truthfullness");

if(myReader.IsDBNull(myOrdinal))
{
  theArticle.Truthfulness = -1;
}
else
{
  theArticle.Truthfulness = myReader.GetInt32(myOrdinal);
}

As Mike Hofer points out in his answer, you could also wrap all this logic into an extension method:

public static class SqlDataReaderExtensions 
{
    public static int SafeGetInt32(this SqlDataReader reader, 
                                   string columnName, int defaultValue) 
    {
        int ordinal = reader.GetOrdinal(columnName);

        if(!reader.IsDbNull(ordinal))
        {
           return reader.GetInt32(ordinal);
        } 
        else
        {
           return defaultValue;
        }
    }
}

and then just use that "SafeGetInt32" method instead:

  theArticle.Truthfulness = myReader.SafeGetInt32("Truthfullness", -1);

Marc

marc_s
or catch the exception and deal with it
Mark
Yes, you could do that too - but avoiding an exception is better than catching and handling one (in general)
marc_s
@marc_s :Agreed with your comment on my answer. Deleting it. Thanks for making it clear. Your comment was "I don't tiink this will work, since if the db column is NULL, the .GetInt32() call will fail with an exception - you won't get back a NULL value which you can then feed into the "??" operator...."
Mahin
+1  A: 

Did you check, SqlDataReader.IsDBNull Method? Probably something like:

if(myReader.IsDBNull(myReader.GetOrdinal("Truthfulness"))
theArticle.Truthfulness = string.Empty;
else
theArticle.Truthfulness = ((myReader.GetInt32(myReader.GetOrdinal("Truthfulness"))))
KMan
well, string.Empty won't do much good if you're dealing with a int32 property......
marc_s
+1  A: 

You know, I deal with this all the time in Oracle. To clean the code up, I wrote a set of extension methods to simplify the operation:

using System.Data.OracleClient;
public static class OracleDataReaderExtensions 
{
    public static int GetInt32(this OracleDataReader reader, string columnName, int defaultValue) 
    {
        return reader.GetInt32(reader.GetOrdinal(columnName)) != DbNull.Value ? 
               reader.GetInt32(reader.GetOrdinal(columnName)) : 
               defaultValue;
    }
}

Create a separate overload for each type you want to return. I primarily work with string, int, date, and decimal. Remember YAGNI (you don't need to work with every type supported by the reader, only those you actually use.)

An extension class like this for SQL Server is really easy to write, and will VASTLY simplify your work. Trust me on that. Would I lie to you? :)

Mike Hofer
I don't think this will work, since if the column in the database is NULL, then the call to GetInt32() will cause an exception. You cannot compare that GetInt32() call against DBNull.Value and react on that....
marc_s
Actually, it does work. I use it every day.
Mike Hofer
The reason it works is because we're using the short-circuiting ?: operator. If it's not null, we return it. Otherwise, we return the default value.
Mike Hofer
actually, at least with SQL Server, it does *NOT* work - GetInt32() will throw an exception if the underlying db column contains NULL, and you cannot compare the GetInt32() call to DBNull.Value - VS2008 will complain and not even compile it.
marc_s
But I definitely like the idea of wrapping this up in an extension method - makes life a lot easier!
marc_s
I tried to use your approach and compare the output of reader.GetInt32() to DBNull.Value in SQL Server: all I get is this error message: Operator '!=' cannot be applied to operands of type 'int' and 'System.DBNull'
marc_s
And if I try to just compare reader.GetInt32() to NULL, I get a `SqlNullValueException` - so I guess SQL Server and Oracle and their readers are behaving a bit differently here....
marc_s
That's very interesting; I'm at home, but I'll take a look at the version of the extension methods I have for SQL Server at work. I may have had to break it out.
Mike Hofer
A: 

Here is what we use on SQLServer and it works like a charm:

...

Dim X as Object = pbDr("TotAmt") 'dr is dim'ed as a DataReader

...

Public Function pbDr(ByVal drName As String) As Object

Dim SQLError As SqlClient.SqlException

Dim IsNull As Boolean

Dim Ordinal, DispNbr As Integer

Try
  Ordinal = dr.GetOrdinal(drName)
  IsNull = dr.IsDBNull(Ordinal)
  If IsNull Then
    Dim Dbtype As String = dr.GetFieldType(Ordinal).ToString
    If Dbtype = "System.String" Then
      Return ""
    ElseIf Dbtype = "System.Int32" _
     OrElse Dbtype = "System.Double" _
     OrElse Dbtype = "System.Decimal" _
     OrElse Dbtype = "System.Int16" Then
      Return 0
    Else
      MsgBox("Print This Screen And Send To Support" _
       & "pbdr-Object = " & Dbtype, MsgBoxStyle.Critical)
      Return ""
    End If
  Else
    Return dr(Ordinal)
  End If

Catch sqlerror
  Call DispSQLError(SQLError, "pbDr")
  pbDr = ""
End Try

End Function

Price Brattin
A: 

IsDbNull(int) is usually much slower that using methods like GetSqlInt32 and then comparing to DBNull.Value or using it's own .IsNull Like:

    public static int Int32(this SqlDataReader r, int ord)
    {
        var t = r.GetSqlInt32(ord);
        return t.IsNull ? default(int) : t.Value;
    }

Tried a few template solutions but to no avail so far. The problem is that all Sql-types (SqlInt32 here) types are actually structs and while they all have .Value property C# doesn't have real templates to handle that. Also they have their own INullable interface which has only .IsNull and is not conpatible with Nyllable<>.

I suspect that one would need full set of Sql-types as C# templates or to add ICOnvertible to them in order to be able to have just one or two templated methods.

If someone has maybe an idea with a functional trick or two speak up :-)

ZXX