Imports System.Runtime.CompilerServices
Imports System.Data.Objects.DataClasses
Imports System.Runtime.Serialization
Imports System.IO
Imports System.Reflection
Imports System.Runtime.Serialization.Formatters.Binary

Namespace Extensiones
    Public Module IEnumerableExtensions

        <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
End Namespace

Public Class ObjectShredder(Of T)
    Private _fi As FieldInfo()
    Private _pi As PropertyInfo()
    Private _ordinalMap As Dictionary(Of String, Integer)
    Private _type As Type

    Public Sub New()
        _type = GetType(T)
        _fi = _type.GetFields()
        _pi = _type.GetProperties.Where(Function(x) Not (x.PropertyType.Name.Contains("EntityReference") Or x.PropertyType.Name.Contains("EntityCollection") Or x.PropertyType.Name.Contains("EntityState") Or x.PropertyType.Name.Contains("EntityKey") Or x.PropertyType.BaseType.Name = "EntityObject")).ToArray
        _ordinalMap = New Dictionary(Of String, Integer)()
    End Sub

    Public Function Shred(ByVal source As IEnumerable(Of T), ByVal table As DataTable, ByVal options As LoadOption?) As DataTable
        If GetType(T).IsPrimitive Then
            Return ShredPrimitive(source, table, options)
        End If

        If table Is Nothing Then
            table = New DataTable(GetType(T).Name)
        End If

        table = ExtendTable(table, GetType(T))
        table.BeginLoadData()

        Using e As IEnumerator(Of T) = source.GetEnumerator()

            While e.MoveNext()

                If options IsNot Nothing Then
                    table.LoadDataRow(ShredObject(table, e.Current), CType(options, LoadOption))
                Else
                    table.LoadDataRow(ShredObject(table, e.Current), True)
                End If
            End While
        End Using

        table.EndLoadData()
        Return table
    End Function

    Public Function ShredPrimitive(ByVal source As IEnumerable(Of T), ByVal table As DataTable, ByVal options As LoadOption?) As DataTable
        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

        table.BeginLoadData()

        Using e As IEnumerator(Of T) = source.GetEnumerator()
            Dim values As Object() = New Object(table.Columns.Count - 1) {}

            While e.MoveNext()
                values(table.Columns("Value").Ordinal) = e.Current

                If options IsNot Nothing Then
                    table.LoadDataRow(values, CType(options, LoadOption))
                Else
                    table.LoadDataRow(values, True)
                End If
            End While
        End Using

        table.EndLoadData()
        Return table
    End Function

    Public Function ExtendTable(ByVal table As DataTable, ByVal type As Type) As DataTable
        For Each f As FieldInfo In type.GetFields()

            If Not _ordinalMap.ContainsKey(f.Name) Then
                Dim dc As DataColumn = If(table.Columns.Contains(f.Name), table.Columns(f.Name), table.Columns.Add(f.Name, f.FieldType))
                _ordinalMap.Add(f.Name, dc.Ordinal)
            End If
        Next
        Dim Propiedades = type.GetProperties.Where(Function(x) Not (x.PropertyType.Name.Contains("EntityReference") Or x.PropertyType.Name.Contains("EntityCollection") Or x.PropertyType.Name.Contains("EntityState") Or x.PropertyType.Name.Contains("EntityKey") Or x.PropertyType.BaseType.Name = "EntityObject"))
        For Each p As PropertyInfo In Propiedades
            If Not _ordinalMap.ContainsKey(p.Name) Then
                Try
                    Dim propiedad = p.PropertyType
                    If propiedad Is GetType(Integer?) Then
                        propiedad = GetType(Integer)
                    ElseIf propiedad Is GetType(Double?) Then
                        propiedad = GetType(Double)
                    ElseIf propiedad Is GetType(Long?) Then
                        propiedad = GetType(Long)
                    ElseIf propiedad Is GetType(DateTime?) Then
                        propiedad = GetType(DateTime)
                    ElseIf propiedad Is GetType(Date?) Then
                        propiedad = GetType(Date)
                    ElseIf propiedad Is GetType(Boolean?) Then
                        propiedad = GetType(Boolean)
                    End If
                    Dim dc As DataColumn = If(table.Columns.Contains(p.Name), table.Columns(p.Name), table.Columns.Add(p.Name, propiedad))
                    _ordinalMap.Add(p.Name, dc.Ordinal)
                Catch ex As Exception
                    Debug.Write(ex.Message)
                End Try
            End If
        Next

        Return table
    End Function

    Public Function ShredObject(ByVal table As DataTable, ByVal instance As T) As Object()
        Dim fi As FieldInfo() = _fi
        Dim pi As PropertyInfo() = _pi

        If instance.[GetType]() <> GetType(T) Then
            ExtendTable(table, instance.[GetType]())
            fi = instance.[GetType]().GetFields()
            pi = instance.[GetType]().GetProperties.Where(Function(x) Not (x.PropertyType.Name.Contains("EntityReference") Or x.PropertyType.Name.Contains("EntityCollection") Or x.PropertyType.Name.Contains("EntityState") Or x.PropertyType.Name.Contains("EntityKey") Or x.PropertyType.BaseType.Name = "EntityObject")).ToArray
        End If

        Dim values As Object() = New Object(table.Columns.Count - 1) {}

        For Each f As FieldInfo In fi
            values(_ordinalMap(f.Name)) = f.GetValue(instance)
        Next

        For Each p As PropertyInfo In pi
            values(_ordinalMap(p.Name)) = p.GetValue(instance, Nothing)
        Next

        Return values
    End Function
End Class


