4

I am trying to write a generic method that will convert a DataTable to a list of strongly typed objects.

The code that I'm working with so far is...

public List<T> ImportTable<T>(String fileName, String table)
{
    //Establish Connection to Access Database File
    var mdbData = new ConnectToAccess(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=F:\ACCESS\" + fileName + ".mdb;");

    var tableData = new List<T>();

    foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
    {
        tableData.Add(ConvertRowToType<T>(row));
    }

    return tableData;
}

public T ConvertRowToType<T>(DataRow row)
{
    //??? What is the best thing to do here ???        
}

I'm not fixated on this code if anybody's suggestions would require changes to it.

So let's say I call this function passing in the type...

public class mdbConcern
{
    public Int32 ConcernId { get; set; }
    public String Concern { get; set; }
}

And the Data coming back in the DataTable looks like...

ConcernID  Concern
1          Law and Ethics
2          Mail
3          Business English
...        ...

What would be the best way to implement the ConvertRowToType(DataRow row) method?

Can someone show me how to use Func as one of the parameters so I can pass in some mapping information?

1

2 Answers 2

8

I think an extension method is the best way to go:

public static class Helper
{
    public static T ToType<T>(this DataRow row) where T : new()
    {
        T obj = new T();
        var props = TypeDescriptor.GetProperties(obj);
        foreach (PropertyDescriptor prop in props)
        {
            if(row.Table.Columns.IndexOf(prop.Name) >= 0 
                && row[prop.Name].GetType() == prop.PropertyType)
            {   
                prop.SetValue(obj, row[prop.Name]);
            }
        }
        return obj;
    }
}

Usage:

public List<T> ImportTable<T>(String fileName, String table)
{
    //Establish Connection to Access Database File
    var mdbData = new ConnectToAccess(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=F:\ACCESS\" + fileName + ".mdb;");

    var tableData = new List<T>();

    foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
    {
        tableData.Add(row.ToType<T>());
    }

    return tableData;
}

Update I see that you asked for a Func that would provide the mapping. I'm not sure exactly what you envisioned but here is a method I came up with:

public class mdbConcern
{
    public Int32 ConcernId { get; set; }
    public String Concern { get; set; }

    public static PropertyDescriptor Mapping(string name)
    {
        PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(mdbConcern));
        switch (name)
        {
            case "Concern_Id":
                return props.GetByName("ConcernId");
            case "Concern":
                return props.GetByName("Concern");
            default:
                return null;
        }
    }
}

public static class Helper
{
    public static T ToType<T>(this DataRow row, Func<string, PropertyDescriptor> mapping) 
       where T : new()
    {
        T obj = new T();        
        foreach (DataColumn col in row.Table.Columns)
        {
            var prop = mapping(col.ColumnName);
            if(prop != null)
                prop.SetValue(obj, row[col]);
        }
        return obj;
    }
}

Usage:

foreach (DataRow row in mdbData.GetData("SELECT * FROM " + table).Rows)
{
    tableData.Add(row.ToType<mdbConcern>(mdbConcern.Mapping));
}

Here's a version using attributes on the type's properties to store its mapping. I think it's a more natural solution:

[AttributeUsage(AttributeTargets.Property)]
public class ColumnMappingAttribute : Attribute
{
    public string Name { get; set; }
    public ColumnMappingAttribute(string name)
    {
        Name = name;
    }
}
public class mdbConcern
{
    ColumnMapping("Concern_Id")]
    public Int32 ConcernId { get; set; }
    ColumnMapping("Concern")]
    public String Concern { get; set; }
}

public static class Helper
{   
    public static T ToType<T>(this DataRow row) where T : new()
    {
        T obj = new T();
        var props = TypeDescriptor.GetProperties(obj);
        foreach (PropertyDescriptor prop in props)
        {
            var columnMapping = prop.Attributes.OfType<ColumnMappingAttribute>().FirstOrDefault();

            if(columnMapping != null)
            {
                if(row.Table.Columns.IndexOf(columnMapping.Name) >= 0 
                    && row[columnMapping.Name].GetType() == prop.PropertyType)
                {               
                    prop.SetValue(obj, row[columnMapping.Name]);
                }
            }
        }
        return obj;
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is a neat code sample, but unfortunately I don't think it is going to work for my situation. The data source I'm pulling from is an old Access database and many column names throughout the schema had spaces in them. As such, the properties on my objects don't all match the column names in the tables they are supposed to correspond to.
@jdavis I updated my answer with a method I think is more like what you're looking for. Let me know if it helps.
0

Addition to @Sorax answer. I enhanced ToType method to support Nullable<> type members (using fields instead of properties and TypeInfo instead of TypeDescriptor). It takes whole DataTable object as a paramater and returns IList.

    protected IList<TResult> TableToList<TResult>(DataTable table) where TResult : new()
    {
        var result = new List<TResult>(table.Rows.Count);

        var fields = typeof(TResult).GetTypeInfo().DeclaredFields;

        TResult obj;
        Object colVal;
        var columns = table.Columns;
        var nullableTypeDefinition = typeof(Nullable<>);
        var dbNullType = typeof(DBNull);
        Type[] genericArguments;

        foreach (DataRow row in table.Rows)
        {
            obj = new TResult();

            foreach (var f in fields)
            {
                if (columns.Contains(f.Name))
                {
                    colVal = row[f.Name];
                    if (colVal.GetType() == f.FieldType)
                    {
                        f.SetValue(obj, colVal);
                    }
                    else if (colVal.GetType() != dbNullType && f.FieldType.IsGenericType && 
                             f.FieldType.GetGenericTypeDefinition() == nullableTypeDefinition)
                    {
                            genericArguments = f.FieldType.GetGenericArguments();

                            if (genericArguments.Length > 0 && genericArguments[0] == colVal.GetType())
                            {
                                f.SetValue(obj, colVal);
                            }
                    }
                }
            }

            result.Add(obj);
        }

        return result;
    }

1 Comment

I'm getting an error on the following line of your code var fields = typeof(TResult).GetTypeInfo() Cannot access private method GetTypeInfo here

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.