방법: 제네릭 형식 T가 DataRow가 아닌 CopyToDataTable<T> 구현
대개 DataTable 개체는 데이터 바인딩에 사용됩니다. CopyToDataTable 메서드는 쿼리 결과를 받아서 나중에 데이터 바인딩에 사용할 수 있도록 데이터를 DataTable에 복사합니다. 하지만 CopyToDataTable 메서드는 제네릭 매개 변수 T가 DataRow 형식인 IEnumerable<T> 소스에서만 작동합니다. 이 제한은 유용하지만 이로 인해 일련의 스칼라 형식, 익명 형식을 프로젝션하는 쿼리 또는 테이블 조인을 수행하는 쿼리에서 테이블을 만들지 못하게 됩니다.
이 항목에서는 DataRow 형식 이외의 제네릭 매개 변수 T 형식을 사용할 수 있는 두 가지 사용자 지정 CopyToDataTable<T> 확장을 구현하는 방법을 설명합니다. IEnumerable<T> 소스에서 DataTable을 만드는 로직은 ObjectShredder<T> 클래스에 있으며, 이 클래스는 두 오버로드 CopyToDataTable<T> 확장 메서드로 래핑됩니다. ObjectShredder<T> 클래스의 Shred 메서드는 채워진 DataTable을 반환하며 세 개의 입력 매개 변수로 IEnumerable<T> 소스, DataTable 및 LoadOption 열거형을 사용합니다. 반환된 DataTable의 초기 스키마는 형식 T의 스키마에 기반합니다. 기존 테이블이 입력으로 제공되는 경우 해당 스키마는 형식 T의 스키마와 일치해야 합니다. 형식 T의 각 public 속성 및 필드는 반환된 테이블에서 DataColumn으로 변환됩니다. 소스 시퀀스에 T에서 파생된 형식이 포함되는 경우 추가 public 속성 또는 필드를 사용할 수 있도록 반환된 테이블 스키마가 확장됩니다.
사용자 지정 CopyToDataTable<T> 메서드를 사용하는 예제는 쿼리에서 DataTable 만들기(LINQ to DataSet)를 참조하십시오.
응용 프로그램에서 사용자 지정 CopyToDataTable<T> 메서드를 구현하려면
IEnumerable<T> 소스에서 DataTable을 만드는 ObjectShredder<T> 클래스를 구현합니다.
Public Class ObjectShredder(Of T) ' Fields Private _fi As FieldInfo() Private _ordinalMap As Dictionary(Of String, Integer) Private _pi As PropertyInfo() Private _type As Type ' Constructor Public Sub New() Me._type = GetType(T) Me._fi = Me._type.GetFields Me._pi = Me._type.GetProperties Me._ordinalMap = New Dictionary(Of String, Integer) End Sub Public Function ShredObject(ByVal table As DataTable, ByVal instance As T) As Object() Dim fi As FieldInfo() = Me._fi Dim pi As PropertyInfo() = Me._pi If (Not instance.GetType Is GetType(T)) Then ' If the instance is derived from T, extend the table schema ' and get the properties and fields. Me.ExtendTable(table, instance.GetType) fi = instance.GetType.GetFields pi = instance.GetType.GetProperties End If ' Add the property and field values of the instance to an array. Dim values As Object() = New Object(table.Columns.Count - 1) {} Dim f As FieldInfo For Each f In fi values(Me._ordinalMap.Item(f.Name)) = f.GetValue(instance) Next Dim p As PropertyInfo For Each p In pi values(Me._ordinalMap.Item(p.Name)) = p.GetValue(instance, Nothing) Next ' Return the property and field values of the instance. Return values End Function ' Summary: Loads a DataTable from a sequence of objects. ' source parameter: The sequence of objects to load into the DataTable.</param> ' table parameter: The input table. The schema of the table must match that ' the type T. If the table is null, a new table is created ' with a schema created from the public properties and fields ' of the type T. ' options parameter: Specifies how values from the source sequence will be applied to ' existing rows in the table. ' Returns: A DataTable created from the source sequence. Public Function Shred(ByVal source As IEnumerable(Of T), ByVal table As DataTable, ByVal options As LoadOption?) As DataTable ' Load the table from the scalar sequence if T is a primitive type. If GetType(T).IsPrimitive Then Return Me.ShredPrimitive(source, table, options) End If ' Create a new table if the input table is null. If (table Is Nothing) Then table = New DataTable(GetType(T).Name) End If ' Initialize the ordinal map and extend the table schema based on type T. table = Me.ExtendTable(table, GetType(T)) ' Enumerate the source sequence and load the object values into rows. table.BeginLoadData() Using e As IEnumerator(Of T) = source.GetEnumerator Do While e.MoveNext If options.HasValue Then table.LoadDataRow(Me.ShredObject(table, e.Current), options.Value) Else table.LoadDataRow(Me.ShredObject(table, e.Current), True) End If Loop End Using table.EndLoadData() ' Return the table. Return table End Function Public Function ShredPrimitive(ByVal source As IEnumerable(Of T), ByVal table As DataTable, ByVal options As LoadOption?) As DataTable ' Create a new table if the input table is null. If (table Is Nothing) Then table = New DataTable(GetType(T).Name) End If If Not table.Columns.Contains("Value") Then table.Columns.Add("Value", GetType(T)) End If ' Enumerate the source sequence and load the scalar values into rows. table.BeginLoadData() Using e As IEnumerator(Of T) = source.GetEnumerator Dim values As Object() = New Object(table.Columns.Count - 1) {} Do While e.MoveNext values(table.Columns.Item("Value").Ordinal) = e.Current If options.HasValue Then table.LoadDataRow(values, options.Value) Else table.LoadDataRow(values, True) End If Loop End Using table.EndLoadData() ' Return the table. Return table End Function Public Function ExtendTable(ByVal table As DataTable, ByVal type As Type) As DataTable ' Extend the table schema if the input table was null or if the value ' in the sequence is derived from type T. Dim f As FieldInfo Dim p As PropertyInfo For Each f In type.GetFields If Not Me._ordinalMap.ContainsKey(f.Name) Then Dim dc As DataColumn ' Add the field as a column in the table if it doesn't exist ' already. dc = IIf(table.Columns.Contains(f.Name), table.Columns.Item(f.Name), table.Columns.Add(f.Name, f.FieldType)) ' Add the field to the ordinal map. Me._ordinalMap.Add(f.Name, dc.Ordinal) End If Next For Each p In type.GetProperties If Not Me._ordinalMap.ContainsKey(p.Name) Then ' Add the property as a column in the table if it doesn't exist ' already. Dim dc As DataColumn dc = IIf(table.Columns.Contains(p.Name), table.Columns.Item(p.Name), table.Columns.Add(p.Name, p.PropertyType)) ' Add the property to the ordinal map. Me._ordinalMap.Add(p.Name, dc.Ordinal) End If Next ' Return the table. Return table End Function End Class
public class ObjectShredder<T> { private System.Reflection.FieldInfo[] _fi; private System.Reflection.PropertyInfo[] _pi; private System.Collections.Generic.Dictionary<string, int> _ordinalMap; private System.Type _type; // ObjectShredder constructor. public ObjectShredder() { _type = typeof(T); _fi = _type.GetFields(); _pi = _type.GetProperties(); _ordinalMap = new Dictionary<string, int>(); } /// <summary> /// Loads a DataTable from a sequence of objects. /// </summary> /// <param name="source">The sequence of objects to load into the DataTable.</param> /// <param name="table">The input table. The schema of the table must match that /// the type T. If the table is null, a new table is created with a schema /// created from the public properties and fields of the type T.</param> /// <param name="options">Specifies how values from the source sequence will be applied to /// existing rows in the table.</param> /// <returns>A DataTable created from the source sequence.</returns> public DataTable Shred(IEnumerable<T> source, DataTable table, LoadOption? options) { // Load the table from the scalar sequence if T is a primitive type. if (typeof(T).IsPrimitive) { return ShredPrimitive(source, table, options); } // Create a new table if the input table is null. if (table == null) { table = new DataTable(typeof(T).Name); } // Initialize the ordinal map and extend the table schema based on type T. table = ExtendTable(table, typeof(T)); // Enumerate the source sequence and load the object values into rows. table.BeginLoadData(); using (IEnumerator<T> e = source.GetEnumerator()) { while (e.MoveNext()) { if (options != null) { table.LoadDataRow(ShredObject(table, e.Current), (LoadOption)options); } else { table.LoadDataRow(ShredObject(table, e.Current), true); } } } table.EndLoadData(); // Return the table. return table; } public DataTable ShredPrimitive(IEnumerable<T> source, DataTable table, LoadOption? options) { // Create a new table if the input table is null. if (table == null) { table = new DataTable(typeof(T).Name); } if (!table.Columns.Contains("Value")) { table.Columns.Add("Value", typeof(T)); } // Enumerate the source sequence and load the scalar values into rows. table.BeginLoadData(); using (IEnumerator<T> e = source.GetEnumerator()) { Object[] values = new object[table.Columns.Count]; while (e.MoveNext()) { values[table.Columns["Value"].Ordinal] = e.Current; if (options != null) { table.LoadDataRow(values, (LoadOption)options); } else { table.LoadDataRow(values, true); } } } table.EndLoadData(); // Return the table. return table; } public object[] ShredObject(DataTable table, T instance) { FieldInfo[] fi = _fi; PropertyInfo[] pi = _pi; if (instance.GetType() != typeof(T)) { // If the instance is derived from T, extend the table schema // and get the properties and fields. ExtendTable(table, instance.GetType()); fi = instance.GetType().GetFields(); pi = instance.GetType().GetProperties(); } // Add the property and field values of the instance to an array. Object[] values = new object[table.Columns.Count]; foreach (FieldInfo f in fi) { values[_ordinalMap[f.Name]] = f.GetValue(instance); } foreach (PropertyInfo p in pi) { values[_ordinalMap[p.Name]] = p.GetValue(instance, null); } // Return the property and field values of the instance. return values; } public DataTable ExtendTable(DataTable table, Type type) { // Extend the table schema if the input table was null or if the value // in the sequence is derived from type T. foreach (FieldInfo f in type.GetFields()) { if (!_ordinalMap.ContainsKey(f.Name)) { // Add the field as a column in the table if it doesn't exist // already. DataColumn dc = table.Columns.Contains(f.Name) ? table.Columns[f.Name] : table.Columns.Add(f.Name, f.FieldType); // Add the field to the ordinal map. _ordinalMap.Add(f.Name, dc.Ordinal); } } foreach (PropertyInfo p in type.GetProperties()) { if (!_ordinalMap.ContainsKey(p.Name)) { // Add the property as a column in the table if it doesn't exist // already. DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name] : table.Columns.Add(p.Name, p.PropertyType); // Add the property to the ordinal map. _ordinalMap.Add(p.Name, dc.Ordinal); } } // Return the table. return table; } }
클래스에서 사용자 지정 CopyToDataTable<T> 확장 메서드를 구현합니다.
Public Module CustomLINQtoDataSetMethods <Extension()> _ Public Function CopyToDataTable(Of T)(ByVal source As IEnumerable(Of T)) As DataTable Return New ObjectShredder(Of T)().Shred(source, Nothing, Nothing) End Function <Extension()> _ Public Function CopyToDataTable(Of T)(ByVal source As IEnumerable(Of T), ByVal table As DataTable, ByVal options As LoadOption?) As DataTable Return New ObjectShredder(Of T)().Shred(source, table, options) End Function End Module
public static class CustomLINQtoDataSetMethods { public static DataTable CopyToDataTable<T>(this IEnumerable<T> source) { return new ObjectShredder<T>().Shred(source, null, null); } public static DataTable CopyToDataTable<T>(this IEnumerable<T> source, DataTable table, LoadOption? options) { return new ObjectShredder<T>().Shred(source, table, options); } }
응용 프로그램에 ObjectShredder<T> 클래스 및 CopyToDataTable<T> 확장 메서드를 추가합니다.
Module Module1 Sub Main() ' Your application code using CopyToDataTable<T>. End Sub End Module Public Module CustomLINQtoDataSetMethods … End Module Public Class ObjectShredder(Of T) … End Class
-
class Program { static void Main(string[] args) { // Your application code using CopyToDataTable<T>. } } public static class CustomLINQtoDataSetMethods { … } public class ObjectShredder<T> { … }
참고 항목
개념
쿼리에서 DataTable 만들기(LINQ to DataSet)