4
\$\begingroup\$

This method assigns the value of a specified column in a DataRow to specified property in an object.

I have 3 conditionals handling exceptions, and I want these conditions to throw exceptions.

The final condition just allows the use of a conversion method if it exists. Is there a better way to test for and throw the exceptions?

public class ColumnToPropertyMap<colType, propType>
{
    public string ColumnName { get; private set; }
    public string PropertyName { get; private set; }
    private Func<colType, propType> conversion;        

    public virtual void Map<T>(T obj, DataRow row)
    {
        if (!row.Table.Columns.Contains(this.ColumnName))
        {
            throw new ArgumentException(
                String.Format(@"Column ""{0}"" does not exist in the row.", this.ColumnName));
        }
        PropertyInfo property = obj.GetType().GetProperty(this.PropertyName);
        if (property == null)
        {
            throw new ArgumentException(String.Format(@"Property ""{0}"" does not exist for object.", this.PropertyName));
        }
        var value = row.IsNull(this.ColumnName) ? row[this.ColumnName].GetDefaultValue(typeof(colType)) : row[this.ColumnName];
        if (conversion == null && property.PropertyType != row.Table.Columns[this.ColumnName].DataType)
        {
            throw new InvalidCastException(
                string.Format(@"Mapping for {0} to {1} failed. Unable to convert {2} to {3}.", this.ColumnName, this.PropertyName, value.GetType(), property.PropertyType));
        }
        if (conversion != null)
        {
            value = conversion((colType)value);
        }
        property.SetValue(obj, value, null);
    }
}

Incorporating @Leonid and @SmartLemons comments/answers into consideration, I changed it to this:

    public virtual void Map<T>(T obj, DataRow row)
    {
        PropertyInfo property = obj.GetType().GetProperty(this.PropertyName);
        ValidateArguments(property, row);
        var value = row.IsNull(this.ColumnName) ? row[this.ColumnName].GetDefaultValue(typeof(colType)) : row[this.ColumnName];
        if (conversion != null)
        {
            value = conversion((colType)value);
        }
        property.SetValue(obj, value, null);
    }
    private void ValidateArguments(PropertyInfo property, DataRow row)
    {
        if (!row.Table.Columns.Contains(this.ColumnName))
        {
            throw new ArgumentException(
                String.Format(@"Column ""{0}"" does not exist in the row.", this.ColumnName));
        }
        if (property == null)
        {
            throw new ArgumentException(String.Format(@"Property ""{0}"" does not exist for object.", this.PropertyName));
        }
        if (conversion == null && property.PropertyType != row.Table.Columns[this.ColumnName].DataType)
        {
            throw new InvalidCastException(
                string.Format(@"Mapping for {0} to {1} failed. Unable to convert {2} to {3}.", this.ColumnName, this.PropertyName, row.Table.Columns[this.ColumnName].DataType.Name, property.PropertyType));
        }
    }

Takes the argument validation out of the method and makes the intent of the Map method clearer.

\$\endgroup\$
6
  • \$\begingroup\$ What do you not like about your approach? \$\endgroup\$
    – Leonid
    Commented Mar 11, 2013 at 19:28
  • \$\begingroup\$ It's not that I don't like it, I was more wondering if someone had a better, or different way. The multiple ifs seem slightly unwieldy, but I didn't see another way to go about it. \$\endgroup\$ Commented Mar 11, 2013 at 19:33
  • 2
    \$\begingroup\$ The first if could be its own method. The line that creates a property and checks it could be itself a private property. The following if can also be its own method. You cannot skip the logic, but you can cut it into smaller chunks. \$\endgroup\$
    – Leonid
    Commented Mar 11, 2013 at 19:45
  • \$\begingroup\$ I had considered creating a private PropertyInfo property for the class, but that would require passing in either the obj that is having the properties mapped, or the PropertyInfo object as part of the constructor, and I wanted to keep the constructor as simple as possible. (Right now, it's just public ColumnToPropertyMap(string columnName, string propertyName){}) \$\endgroup\$ Commented Mar 11, 2013 at 20:22
  • \$\begingroup\$ @Leonid You should put your second comment as an answer. \$\endgroup\$ Commented Mar 11, 2013 at 21:10

1 Answer 1

2
\$\begingroup\$

Your code is as simple as it can get... but if you are wanting to clean up the method then you could do something like this to make it look nicer.

public string ColumnName { get; private set; }
    public string PropertyName { get; private set; }
    private Func<colType, propType> conversion;

    public virtual void Map<T>(T obj, DataRow row)
    {
        Conditions.Check1(row, this.ColumnName).argerror(String.Format(@"Column ""{0}"" does not exist in the row.", this.ColumnName));
        PropertyInfo property = obj.GetType().GetProperty(this.PropertyName);
        Conditions.Check2(property).argerror(String.Format(@"Property ""{0}"" does not exist for object.", this.PropertyName));
        var value = row.IsNull(this.ColumnName) ? row[this.ColumnName].GetDefaultValue(typeof(colType)) : row[this.ColumnName];
        Conditions.Check3(conversion, property, row, this.ColumnName).casterror(string.Format(@"Mapping for {0} to {1} failed. Unable to convert {2} to {3}.", this.ColumnName, this.PropertyName, value.GetType(), property.PropertyType));
        if (conversion != null)
        {
            value = conversion((colType)value);
        }
        property.SetValue(obj, value, null);
    }

    public static class Conditions
    {
        public class Error
        {
            private bool error = false;
            public bool isError { get {return error;}}

            public Error(bool r)
            {
                error = r;
            }
            public bool argerror(string message)
            {
                if (error)
                    throw new ArgumentException(message);
                return error;
            }
            public bool casterror(string message)
            {
                if (error)
                    throw new InvalidCastException(message);
                return error;
            }

        }
        public static Error Check1(dynamic a, string cn)
        {
            return new Error(a.Table.Columns.Contains(cn));
        }
        public static Error Check2(dynamic a)
        {
            return new Error(a == null);
        }
        public static Error Check3(dynamic a, dynamic b, dynamic c, string cn)
        {
            return new Error(a == null && b.PropertyType != c.Table.Columns[cn].DataType);
        }
    }
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.