Skip to content

Instantly share code, notes, and snippets.

@gyuwon
Last active August 29, 2015 14:10
Show Gist options
  • Save gyuwon/cd41863e97d817d4436e to your computer and use it in GitHub Desktop.
Save gyuwon/cd41863e97d817d4436e to your computer and use it in GitHub Desktop.
DataTable to List<T>
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
namespace DataTableMapper
{
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public double Value { get; set; }
public override string ToString()
{
return string.Format(@"{{ ""Id"": {0}, ""Name"": ""{1}"", ""Value"": {2} }}", Id, Name, Value);
}
}
public static class Program
{
/// <summary>
/// <see cref="DataTable"/> 개체를 <see cref="List&lt;T&gt;"/>로 변환합니다.
/// </summary>
/// <typeparam name="T"><see cref="DataRow"/>를 투영할 대상 형식입니다.</typeparam>
/// <param name="table">소스 테이블입니다.</param>
/// <returns><paramref name="table"/>을 변환한 <see cref="List&lt;T&gt;"/>입니다.</returns>
public static List<T> ToList<T>(DataTable table)
where T : new()
{
return ToList(table, () => new T(), (c, p) => c.ColumnName == p.Name);
}
/// <summary>
/// <see cref="DataTable"/> 개체를 <see cref="List&lt;T&gt;"/>로 변환합니다.
/// </summary>
/// <typeparam name="T"><see cref="DataRow"/>를 투영할 대상 형식입니다.</typeparam>
/// <param name="table">소스 테이블입니다.</param>
/// <param name="factory"><typeparamref name="T"/> 개체를 생성하는 메서드입니다.</param>
/// <param name="match"><see cref="DataColumn"/>과 속성의 일치 여부를 반환하는 메서드입니다.</param>
/// <returns><paramref name="table"/>을 변환한 <see cref="List&lt;T&gt;"/>입니다.</returns>
public static List<T> ToList<T>(DataTable table,
Func<T> factory,
Func<DataColumn, PropertyInfo, bool> match)
{
// 조회와 설정이 가능한 공용 인스턴스 속성 목록을 가져옵니다.
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.SetProperty);
// 속성 값을 설정하는 메서드 대리자를 빌드하는 GetSetter<,>(MethodInfo) 메서드의 제네릭 정의를 가져옵니다.
// 대리자를 빌드하는 목적은 성능적으로 불리한 리플렉션 작업을 최소하하는 것입니다.
var getSetterTemplate = typeof(Program).GetMethod("GetSetter", BindingFlags.NonPublic | BindingFlags.Static);
// 데이터 열 값을 속성에 투영하는 프로세스 목록을 캐싱합니다.
var setters = table.Columns
.Cast<DataColumn>()
.Select((column, index) =>
{
// column과 일치하는 속성을 찾습니다. 둘 이상의 속성이 일치하면 예외가 발생합니다.
var property = properties.SingleOrDefault(p => match(column, p));
if (property != null &&
property.PropertyType.IsAssignableFrom(column.DataType))
{
// column과 일치하는 속성이 존재하고 데이터 값을 속성에 대입할 수 있는 경우입니다.
// 속성 형식에 대한 GetSetter<,>(MethodInfo) 제네릭 메서드를 만들어 호출합니다.
var getSetter = getSetterTemplate.MakeGenericMethod(typeof(T), property.PropertyType);
var setter = (Action<T, object>)getSetter.Invoke(null, new object[] { property.GetSetMethod() });
// 열 인덱스와 속성 설정 메서드를 캡슐화해 반환합니다.
return new { ColumnIndex = index, Setter = setter };
}
return null;
})
.Where(setter => setter != null)
.ToList();
// DataRow를 T 형식으로 투영하는 메서드를 정의합니다.
Func<DataRow, T> selector = row =>
{
T instance = factory();
foreach (var setter in setters)
{
var value = row[setter.ColumnIndex];
setter.Setter(instance, value);
}
return instance;
};
// table을 List<>로 변환해 반환합니다.
return table.AsEnumerable().Select(selector).ToList();
}
private static Action<TComponent, object> GetSetter<TComponent, TProperty>(MethodInfo setMethod)
{
// 속성 설정 메서드를 정적(static) 형태로 빌드합니다.
// 정적 형태로 빌드하면 첫번째 매개변수로 인스턴스를 전달하게 됩니다.
var setter = (Action<TComponent, TProperty>)setMethod.CreateDelegate(typeof(Action<TComponent, TProperty>));
// 인스턴스에 속성 값을 설정하는 메서드를 반환합니다.
return (component, propertyValue) => setter(component, (TProperty)propertyValue);
}
public static void Main(string[] args)
{
var table = new DataTable
{
Columns =
{
new DataColumn("Id", typeof(int)),
new DataColumn("Name", typeof(string)),
new DataColumn("Value", typeof(double)),
new DataColumn("NotMapped", typeof(string))
}
};
var row1 = table.NewRow();
row1["Id"] = 1;
row1["Name"] = "Foo";
row1["Value"] = 256;
row1["NotMapped"] = "Hello";
table.Rows.Add(row1);
var row2 = table.NewRow();
row2["Id"] = 2;
row2["Name"] = "Bar";
row2["Value"] = 65536;
row2["NotMapped"] = "World";
table.Rows.Add(row2);
foreach (var item in ToList<Item>(table))
{
Console.WriteLine(item);
}
}
}
}
@gyuwon
Copy link
Author

gyuwon commented Nov 24, 2014

Output:

{ "Id": 1, "Name": "Foo", "Value": 256 }
{ "Id": 2, "Name": "Bar", "Value": 65536 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment