방법: 제네릭 형식 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> 소스, DataTableLoadOption 열거형을 사용합니다. 반환된 DataTable의 초기 스키마는 형식 T의 스키마에 기반합니다. 기존 테이블이 입력으로 제공되는 경우 해당 스키마는 형식 T의 스키마와 일치해야 합니다. 형식 T의 각 public 속성 및 필드는 반환된 테이블에서 DataColumn으로 변환됩니다. 소스 시퀀스에 T에서 파생된 형식이 포함되는 경우 추가 public 속성 또는 필드를 사용할 수 있도록 반환된 테이블 스키마가 확장됩니다.

사용자 지정 CopyToDataTable<T> 메서드 사용에 대한 예제는 쿼리에서 DataTable 만들기를 참조합니다.

애플리케이션에서 사용자 지정 CopyToDataTable<T> 메서드를 구현하려면

  1. 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);
    
  2. 클래스에서 사용자 지정 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
    
  3. 애플리케이션에 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>
{
…
}

참고 항목