Reputation: 904
I have a database with a nullable column foo_date
, where Npgsql maps the sql NULL
value to an instance of the C# class DBNull
. In my C# aggregate I use the type DateTime?
for said column. So the question is how to easily convert DBNull
to a nullable type.
I want to write a utility method like, e.g.,
public static class DbUtil
{
public static T? CastToNullable<T>(object obj)
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
}
which I would like to use like this:
IDataRecord rec = ...
DateTime? fooDate = DbUtil.CastToNullable<DateTime>(rec["foo_date"]);
However, I get the compiler error:
Error CS0403 Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
When I replace return null
by return default(T?)
, the compiler is happy, but the method does not return null
but the default Date
, i.e., 01.01.0001
.
What is the correct way to write the generic utility method above?
Upvotes: 0
Views: 473
Reputation: 16672
It seems like you want to access columns on your NpgsqlDataReader, but to get .NET null for null columns instead of DBNull.Value
. If so, the usual way is to write use an extension method as follows:
public static class DbDataReaderExtensions
{
public static T? GetValueOrDefault<T>(this DbDataReader reader, int ordinal)
where T : class
=> reader.IsDBNull(ordinal) ? null : reader.GetFieldValue<T>(ordinal);
}
This can be used directly on your reader:
var s = reader.GetValueOrDefault<string>(0);
Upvotes: 1
Reputation: 42225
I'm assuming you've got nullable reference types enabled, otherwise that T?
would be a compiler error.
T?
, where T
is a generic type parameter, has multiple meanings unfortunately.
T
is a value type (i.e. you have a where T : struct
constraint), T?
means Nullable<T>
.T
is a reference type (i.e. you have a where T : class
constraint), T?
means a reference type which is allowed to be null.T
is unconstrained, T?
means "If T
is a reference type, then this is allowed to be null; otherwise no effect".In other words, if you have:
T? Foo<T>() => default;
If you call Foo<int>()
, you get back an int
, not an int?
.
If however you have:
T? Foo<T>() where T : struct => default;
then Foo<int>()
returns an int?
.
In other words, your signature needs to be:
public static T? CastToNullable<T>(object obj) where T : struct
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
Upvotes: 2
Reputation: 3651
Please check Nullable structure which is in fact long/real version of T?
In that case your method would be
public static T? CastToNullable<T>(object obj) where T: struct
{
if (DBNull.Value.Equals(obj))
return null;
return new Nullable<T>((T)obj);
}
That would work only for structures, for reference types you could add
public static T CastToNullableObj<T>(object obj) where T: class
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
Upvotes: 2