Last active
August 29, 2015 14:10
-
-
Save gyuwon/cd41863e97d817d4436e to your computer and use it in GitHub Desktop.
DataTable to List<T>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<T>"/>로 변환합니다. | |
/// </summary> | |
/// <typeparam name="T"><see cref="DataRow"/>를 투영할 대상 형식입니다.</typeparam> | |
/// <param name="table">소스 테이블입니다.</param> | |
/// <returns><paramref name="table"/>을 변환한 <see cref="List<T>"/>입니다.</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<T>"/>로 변환합니다. | |
/// </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<T>"/>입니다.</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); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: