2

I am trying to extend the Field method of the DataRow extension to add a parameter to check if the column exists:

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);
}

This works fine for int, datetime etc. However, when I try to use it with string it shows error:

The type string must be non-nullable

I am also getting error if there is a null value in the database:

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

Is there a way it can be extended the Dataextension seamlessly?

Samuel
  • 1,949
  • 4
  • 18
  • 30
  • Possible duplicate of [Can I create a generic method that takes a value type or a reference type but always returns a nullable type](https://stackoverflow.com/questions/19571213/can-i-create-a-generic-method-that-takes-a-value-type-or-a-reference-type-but-al) – dnickless Jul 19 '17 at 21:57
  • doesn't look like exactly what I asked, I could be wrong. I will look at it in detail tomorrow. – Samuel Jul 20 '17 at 03:14

2 Answers2

2

As Markus pointed out, you've got two challenges here. One is about structs vs value types and the other one is to do with the fact that you have to deal with null values.

The second one is trivial to tackle: Just add a ? to your implementation like this: row.Field<T?>(columnName) and your exceptions will be gone.

The first problem, however, is a nasty and frequently encountered one. I am unaware of a pretty way of solving this. Let me still suggest something:

Based on your code above I assume that you are happy to get back Nullable types even for non-nullable columns. So here is something you could do to support reference types on top of what you have and still avoid too much code duplication:

// 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);
}

With this code in place, you get the functionality you want but at a price: You will need to specify type parameters (just like you do now) when you call these methods because type inference won't work (here is why).

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

Also, you will need to differentiate in your calls between the value type version and the reference type version which simply isn't pretty. Why do we need to use two different names for the methods? The reason for that is that you cannot overload methods by simply adding different type constraints (see here).

The type inference topic could be solved by using an out parameter which, however, comes again with a bunch of downsides...

// 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>);
}

Now, you can call your method without the type parameter like this:

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

Unfortunately, this does not work with properties - only with fields.

You see, the world is evil and, sometimes, we cannot do too much about it. ;)

Update based on your comment:

The code below changes your semantics a little but perhaps that's ok:

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);
}

Calling this method would need to look like:

// 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");
dnickless
  • 10,733
  • 1
  • 19
  • 34
  • Thanks for the long answer, I will try these and report here. All I wanted to do is to add a condition to the already existing extension method. Thought it would be trivial. – Samuel Jul 20 '17 at 03:12
1

The reason for the first error message is the where-constraint:

where T : struct

This constraint requires that each type T that is used as a type parameter is a value type. string is a reference type, hence the error message. In order tomsolve the problem, you should remove the constraint if you don't need it.

As regards the Null-value problem, you should check whether the column is null (if it exists) and in this case also return default(T). You can use the DataRow.IsNull method to check whether the cell is null.

Markus
  • 20,838
  • 4
  • 31
  • 55