扩展Datarow扩展方法

时间:2017-07-19 19:21:54

标签: c# extension-methods

我正在尝试扩展DataRow扩展的Field方法,以添加一个参数来检查列是否存在:

public static T? FieldValue<T>(
    this DataRow row,
    string columnName,
    bool checkColumn = false) where T : struct

{
    return
        checkColumn
        && !row.Table.Columns.Contains(
                                       columnName)
        ? default(T)
        : row.Field<T>(
                       columnName);
}

这适用于int,datetime等。但是,当我尝试将它与字符串一起使用时,它显示错误:

The type string must be non-nullable

如果数据库中存在空值,我也会收到错误:

Cannot cast DBNull.Value to type 'System.Decimal'

有没有办法可以无缝扩展Dataextension?

2 个答案:

答案 0 :(得分:2)

正如马库斯指出的那样,你在这里遇到了两个挑战。一个是关于结构与值的类型,另一个是关于你必须处理空值的事实。

第二个问题很容易解决:只需在您的实施中添加?,如下所示:row.Field<T?>(columnName)您的例外将会消失。

然而,第一个问题是一个令人讨厌且经常遇到的问题。我不知道解决这个问题的一种很好的方法。我还是建议一下:

根据您上面的代码,我假设您很高兴能够获得Nullable类型,即使对于非可空列也是如此。因此,您可以采取一些措施来支持您拥有的参考类型,并且仍然可以避免过多的代码重复:

// value type version
public static T? FieldValueStruct<T>(this DataRow row, string columnName, bool checkColumn = false)
    where T : struct
{
    return row.GetValue(columnName, checkColumn, default(T), row.Field<T? /*with a question mark!!!*/ >);
}

// reference type version
public static T FieldValueClass<T>(this DataRow row, string columnName, bool checkColumn = false)
    where T : class
{
    return row.GetValue(columnName, checkColumn, default(T), row.Field<T>);
}

// shared amongst value and reference type implementation
private static T GetValue<T>(this DataRow row, string columnName, bool checkColumn, T defaultValue, Func<string, T> getter)
{
    return checkColumn && !row.Table.Columns.Contains(columnName)
        ? defaultValue
        : getter(columnName);
}

使用此代码,您可以获得所需的功能,但需要付出代价:当您调用这些方法时,您需要指定类型参数(就像您现在一样),因为类型推断不会起作用({ {3}})。

string s;
// no type inference, type parameter must be specified
s = row.FieldValueClass<string>("test");

此外,您需要区分值类型版本和参考类型版本之间的调用,这些版本并不简单。为什么我们需要为这些方法使用两个不同的名称?原因是你不能通过简单地添加不同的类型约束来重载方法(参见here is why)。

类型推断主题可以通过使用out参数来解决,然而,这又带来了一系列缺点......

// value type version that works with type inference
public static void FieldValueStruct<T>(this DataRow row, string columnName, out T? value, bool checkColumn = false)
    where T : struct
{
    value = row.GetValue(columnName, checkColumn, default(T), row.Field<T?>);
}

// reference type version that works with type inference
public static void FieldValueClass<T>(this DataRow row, string columnName, out T value, bool checkColumn = false)
    where T : class
{
    value = row.GetValue(columnName, checkColumn, default(T), row.Field<T>);
}

现在,您可以在没有类型参数的情况下调用您的方法:

string s;
// with type inference, doesn't work with properties, though, only with fields
row.FieldValueClass("test", out s);

不幸的是,这不适用于属性 - 仅适用于字段。

你看,世界是邪恶的,有时我们不能做太多。 ;)

根据您的评论进行更新:

下面的代码稍微改变了你的语义,但也许还可以:

public static T FieldValue<T>(this DataRow row, string columnName, bool checkColumn = false)
{
    return checkColumn && !row.Table.Columns.Contains(columnName)
        ? default(T)
        : row.Field<T>(columnName);
}

调用此方法需要看起来像:

// this will return 0 if the column is not available, a null value from the database will cause an exception
int i = r.FieldValue<int>("test");
// this will return null if the column is not available, a null value from the database would be ok
int? iNullable = r.FieldValue<int?>("test");
// this will return null if the column is not available, a null value from the database would be ok
string s = r.FieldValue<string>("test");

答案 1 :(得分:1)

第一条错误消息的原因是where - 约束:

where T : struct

此约束要求用作类型参数的每个类型T都是值类型。 string是引用类型,因此是错误消息。为了解决问题,如果您不需要,则应删除约束。

关于Null值问题,您应检查列是否为空(如果存在),在这种情况下也返回default(T)。您可以使用DataRow.IsNull方法检查单元格是否为空。