방법: 제네릭 형식 T가 DataRow가 아닌 CopyToDataTable<T> 구현
대개 DataTable 개체는 데이터 바인딩에 사용됩니다. CopyToDataTable 메서드는 쿼리 결과를 받아서 나중에 데이터 바인딩에 사용할 수 있도록 데이터를 DataTable에 복사합니다. 하지만 CopyToDataTable 메서드는 제네릭 매개 변수 IEnumerable<T>가 T
형식인 DataRow 소스에서만 작동합니다. 이 제한은 유용하지만 이로 인해 일련의 스칼라 형식, 익명 형식을 프로젝션하는 쿼리 또는 테이블 조인을 수행하는 쿼리에서 테이블을 만들지 못하게 됩니다.
이 항목에서는 CopyToDataTable<T>
형식 이외의 제네릭 매개 변수 T
형식을 사용할 수 있는 두 가지 사용자 지정 DataRow 확장을 구현하는 방법을 설명합니다. DataTable 소스에서 IEnumerable<T>을 만드는 로직은 ObjectShredder<T>
클래스에 있으며, 이 클래스는 두 오버로드 CopyToDataTable<T>
확장명 메서드로 래핑됩니다. Shred
클래스의 ObjectShredder<T>
메서드는 채워진 DataTable을 반환하며 세 개의 입력 매개 변수로 IEnumerable<T> 소스, DataTable 및 LoadOption 열거형을 사용합니다. 반환된 DataTable의 초기 스키마는 형식 T
의 스키마에 기반합니다. 기존 테이블이 입력으로 제공되는 경우 해당 스키마는 형식 T
의 스키마와 일치해야 합니다. 형식 T
의 각 public 속성 및 필드는 반환된 테이블에서 DataColumn으로 변환됩니다. 소스 시퀀스에 T
에서 파생된 형식이 포함되는 경우 추가 public 속성 또는 필드를 사용할 수 있도록 반환된 테이블 스키마가 확장됩니다.
사용자 지정 CopyToDataTable<T>
메서드 사용에 대한 예제는 쿼리에서 DataTable 만들기를 참조합니다.
애플리케이션에서 사용자 지정 CopyToDataTable<T> 메서드를 구현하려면
ObjectShredder<T>
소스에서 DataTable을 만드는 IEnumerable<T> 클래스를 구현합니다.public class ObjectShredder<T> { readonly FieldInfo[] _fi; readonly PropertyInfo[] _pi; readonly Dictionary<string, int> _ordinalMap; readonly 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. 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(); foreach (T item in source) { if (options != null) { table.LoadDataRow(ShredObject(table, item), (LoadOption)options); } else { table.LoadDataRow(ShredObject(table, item), 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. 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()) { var 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. var 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; } }
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
앞의 예에서는
DataColumn
의 속성과 필드가 nullable 값 형식이 아니라고 가정합니다. nullable 값 형식이 있는 속성 및 필드를 처리하려면 다음 코드를 사용합니다.// Nullable-aware code for properties. DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name] : table.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType); // Nullable-aware code for fields. DataColumn dc = table.Columns.Contains(f.Name) ? table.Columns[f.Name] : table.Columns.Add(f.Name, Nullable.GetUnderlyingType(f.FieldType) ?? f.FieldType);
클래스에서 사용자 지정
CopyToDataTable<T>
확장명 메서드를 구현합니다.public static class CustomLINQtoDataSetMethods { public static DataTable CopyToDataTable<T>(this IEnumerable<T> source) => new ObjectShredder<T>().Shred(source, null, null); public static DataTable CopyToDataTable<T>(this IEnumerable<T> source, DataTable table, LoadOption? options) => new ObjectShredder<T>().Shred(source, table, options); }
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
애플리케이션에
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>
{
…
}