2

I'm trying to write a generic method to read data from SQLDataReader. It works really well except when I want to get custom default value for some data types. For example, for string I want to get string.Empty instead of null.

public static T SafeGetValue<T>(SqlDataReader dr, string columnName)
{
    T returnValue = default(T);
    var value = dr[columnName];

    if (value != null && value != DBNull.Value)
    {
        returnValue = (T)value;
    }
    else
    {
        returnValue.Null();
    }

    return returnValue;
}

public static object Null(this object o)
{
    return null;
}

public static string Null(this string stringValue)
{
    return string.Empty;
}

When T is string, I'm trying to get it to goto Null overload of string but it still goes to the object overload. Is there any way to do this?

4
  • 1
    You could just pass in T defaultValue as a parameter Commented Mar 16, 2017 at 21:27
  • Then I'll have to specify a default value in each call and that would be a problem if we ever change that. Commented Mar 16, 2017 at 21:32
  • Then pass in a default value generator object, so you would only ever need to change it in one place. Commented Mar 16, 2017 at 21:35
  • Do you have an example? The custom default also apply to some other types not only string. Commented Mar 16, 2017 at 21:37

4 Answers 4

4

The Null method is statically bound to the object version. I think your simplest option is to use a switch, or a dictionary, to handle your special cases. Like this:

private static readonly Dictionary<Type, Object> _NullValues = new Dictionary<Type, Object>()
{
    { typeof(String), String.Empty }
};

public static object Null<T>(this T o)
{
    object ret;
    return _NullValues.TryGetValue(typeof(T), out ret)
        ? ret : default(T);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Slick, it is better than separate methods. This is what I'll do if no other better answers come up.
1

To expand on the comment I gave above, I suggested passing in a default value, but you can also pass in a generator instead, something you would only ever need to change in a single place. For example:

public class DefaultValueGenerator<T>
{
    public virtual T Default()
    {
        return default(T);
    }
}

public class StringValueGenerator : DefaultValueGenerator<string>
{
    public override string Default()
    {
        return "";
    }
}

public static T SafeGetValue<T>(SqlDataReader dr, string columnName, 
    DefaultValueGenerator<T> defaultGenerator)
{
    //snip
    returnValue = defaultGenerator.Default();
}

And use it like this:

var stringDefaultGenerator = new StringValueGenerator();
var x = SafeGetValue<string>(dr, "column", sg);

1 Comment

Thanks. I still have to pass something everytime I call SafeGetValue. When in future, we'll have time to refactor the code to not be dependent on crazy defaults, I wouldn't need that parameter.
0

This smells like you're trying to have your data access layer cover up for data-not-present errorhandling in upper layers.

While that may seem like a good idea to avoid some errors globally, it might hurt you in the long run as you might not be able to distinguish between "record not present" and "record has the value I chose to cover up null" in the future. It also won't help you in cases where you'd like to supply a different default for the same type in a different usage scenario.

I just wanted to put this up so that you have a clear idea that there is also a price for taking this path.

Personally, I'd go for a T defaultValue parameter, have some Option<T> as a return type or possibly an out T parameter and a bool return type. This gives up neither compile-time safety, nor per-usage missing record handling.

1 Comment

I've the same concerns. But I'm working on a legacy project and cannot risk changing functionality for now.
0

For such case I'm using optional parameter in method with default value. So you override default behavior only where it's needed:

public static T GetValueOrDefault<T>(SqlDataReader dr, string columnName, T defaultValue = default(T))
{
     T returnValue = default(T);
     var value = dr[columnName];

     return value == null ? (T) defaultValue : value;
}

If you want to go even deeper and handle cases when column doesn't exists or you have deals with Nullable types - here the part of code I'm using in my projects:

public static T GetValueOrDefault<T>(IDataRecord row, string fieldName, T defaultValue = default(T))
{
    if (HasColumn(row, fieldName))
    {
        int ordinal = row.GetOrdinal(fieldName);
        object value = row.GetValue(ordinal);

        return row.IsDBNull(ordinal)
            ? defaultValue
            : ConvertValue<T>(value);
    }
    else
    {
        return defaultValue;
    }
}

public static bool HasColumn(IDataRecord row, string columnName)
{
    for (var i = 0; i < row.FieldCount; i++)
    {
        if (row.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
        {
            return true;
        }
    }
    return false;
}

public static T ConvertValue<T>(object value)
{
    var type = typeof(T);
    type = Nullable.GetUnderlyingType(type) ?? type;

    var result = Convert.ChangeType(value, type);
    return (T)result;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.