4

In this SO post I found a generic extension method which returns a default value if a value read by an SqlDataReader is null or DBNull.Value, and the correctly converted value otherwise. I implemented it like this:

public static T GetValueOrDefault<T>(this SqlDataReader reader, string columnName, T defaultValue = default(T))
{
  object val = reader[columnName];
  if (val == null || val == DBNull.Value)
  {
    return defaultValue;
  }

  return (T)val;
}

The examples mentioned below the method in the post linked above don't use the correct syntax, but nevertheless suggest that this should also work with nullable types such as int?.

However, I read a nullable int column from the database like this:

MyProperty = reader.GetValueOrDefault<int?>("SomeColumnName")

where debug mode shows me that val is -1 and T is int?, but I get an InvalidCastException. From this post I figured it's because unboxing and casting can't be performed in a single operation (val is of type object holding a short value), but what can I do to make it work in my case? Since it's a generic method, I wouldn't know how to manually unbox it before doing the conversion.

Community
  • 1
  • 1
InvisiblePanda
  • 1,589
  • 2
  • 16
  • 39
  • MyProperty = reader.GetValueOrDefault("SomeColumnName") – Tim Schmelter Apr 27 '15 at 08:17
  • @TimSchmelter That still doesn't work. I guess it's because the -1 value is treated as a `short`, but I can't write `reader.GetValueOrDefault` because it could actually hold a larger number in the database. – InvisiblePanda Apr 27 '15 at 08:20
  • 1
    @InvisiblePanda No. What is the database column type? If database can hold a large value, it will not be of type `short` in `SqlDataReader` – Sriram Sakthivel Apr 27 '15 at 08:24
  • @SriramSakthivel You actually pointed me in the right direction. See my comment to the accepted answer below. The type was changed from `int` to `smallint` in the database and I have to change the model POCOs accordingly... – InvisiblePanda Apr 27 '15 at 08:34

1 Answers1

5

For a really good answer, you need to post a good, minimal, complete code example.

If a cast to int fails, then the boxed value isn't an int. You should be able to see in the debugger what it actually is.

That said, the Convert class is a lot more sophisticated about conversions; the cast will require an exact match, i.e. the boxed value must be an int (the literal -1 would be an int, but in your case the value's coming from who-knows-where and easily could be a short or long or whatever).

So instead of the cast, a call e.g. to Convert.ToInt32() would work better.

In your case, with the generic method (i.e. the type is unknown at compile-time), the Convert.ChangeType() method is probably more appropriate:

public static T GetValueOrDefault<T>(this SqlDataReader reader, string columnName, T defaultValue = default(T))
{
  object val = reader[columnName];
  if (val == null || val == DBNull.Value)
  {
    return defaultValue;
  }

  return (T)Convert.ChangeType(val, typeof(T));
}
Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • 3
    Good answer, but I don't think `-1` can be an `uint`. :) –  Apr 27 '15 at 08:28
  • Hey Peter, thanks for the answer. I actually tried creating such an example, but I couldn't reproduce the problem because I didn't understand it well enough :/ That is, I tried to reproduce it by doing stuff like `(int?)((short)-1)` and similar, but never got the above error. Now having read the above comments, I could have done so. – InvisiblePanda Apr 27 '15 at 08:28
  • Actually, I don't want (or didn't want, rather) to convert to `int`, but somehow directly return `int?`. Summarizing the comments below the original post, I found out what the problem was. It's a database "under construction", and the column got changed to `smallint`, so that's the reason I'm getting a `short` value. . . I'll accept your answer anyway, as it provides useful input for me, and because noone could have pointed directly to the actual silly cause of the problem ;) – InvisiblePanda Apr 27 '15 at 08:32
  • @InvisiblePanda: okay, thanks. Glad to hear you tracked the issue down. I should point out that in the generic case, the `ChangeType()` method makes more sense than the `ToInt32()` I proposed. I'll edit my answer to make that clear. – Peter Duniho Apr 27 '15 at 08:34
  • The issue with Convert.ChangeType() is that it's uber slow compared to just casting. I was hoping for another answer, but it looks like I will just have to find a way to control the type that is returned from the reader (by doing a cast in my SQL for example). – oscilatingcretin Dec 30 '15 at 14:28