我有一个foo_date
数据库,其中Npgsql将sqlNULL
值映射到C#类DBNull
的实例。在我的 C# 聚合中,我对所述列使用DateTime?
类型。所以问题是如何轻松地将DBNull
转换为可为空的类型。
我想编写一个实用程序方法,例如
public static class DbUtil
{
public static T? CastToNullable<T>(object obj)
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
}
我想像这样使用:
IDataRecord rec = ...
DateTime? fooDate = DbUtil.CastToNullable<DateTime>(rec["foo_date"]);
但是,我收到编译器错误:
错误 CS0403 无法将 null 转换为类型参数"T",因为它可能是不可为空的值类型。考虑改用"默认(T)"。
当我用return default(T?)
替换return null
时,编译器很高兴,但该方法不返回null
而是返回默认Date
,即01.01.0001
。
编写上述通用实用程序方法的正确方法是什么?
我假设您启用了可为空的引用类型,否则T?
将是编译器错误。
不幸的是,T?
,其中T
是泛型类型参数,具有多种含义。
- 当
T
是值类型(即您有一个where T : struct
约束)时,T?
表示Nullable<T>
。 - 当
T
是引用类型(即您有一个where T : class
约束)时,T?
表示允许为 null 的引用类型。 - 当
T
不受约束时,T?
表示"如果T
是引用类型,则允许为 null;否则无效"。
换句话说,如果您有:
T? Foo<T>() => default;
如果你打电话给Foo<int>()
,你会得到一个int
,而不是一个int?
。
但是,如果您有:
T? Foo<T>() where T : struct => default;
然后Foo<int>()
返回一个int?
。
换句话说,您的签名需要:
public static T? CastToNullable<T>(object obj) where T : struct
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
请检查可为空的结构,它实际上是T?
的长/真实版本
在这种情况下,您的方法是
public static T? CastToNullable<T>(object obj) where T: struct
{
if (DBNull.Value.Equals(obj))
return null;
return new Nullable<T>((T)obj);
}
这仅适用于结构,对于您可以添加的引用类型
public static T CastToNullableObj<T>(object obj) where T: class
{
if (DBNull.Value.Equals(obj))
return null;
return (T)obj;
}
似乎您想访问 NpgsqlDataReader 上的列,但要为空列获取 .NET null 而不是DBNull.Value
.如果是这样,通常的方法是编写使用扩展方法,如下所示:
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);
}
这可以直接在您的阅读器上使用:
var s = reader.GetValueOrDefault<string>(0);