【问题标题】:How to join two or more DataTables together to display in DataGridView如何将两个或多个 DataTable 连接在一起以在 DataGridView 中显示
【发布时间】:2021-05-09 20:14:13
【问题描述】:

我正在尝试找到将两个或多个数据表连接到单个数据表中并将其用作 DataGridView 的数据源的正确方法。我已经阅读了各种示例和帖子,但我尝试过的任何方法似乎都没有奏效。我确定我错过了什么,但不确定是什么。

我有三个表:dt_Record(25 列)、dt_ENDP(35 列)和 dt_ITSM(12 列),每一个都来自不同的数据库服务器。因为我不是这些服务器的管理员,所以我不能以任何方式加入它们。现在我只是想让前两个表加入,所以我有一个如何做的基础。此连接表/DGV 将用于报告,用户可以在该报告中选择他们希望哪些列可见并导出到 Excel。

这是我根据solution here 尝试过的:

Private Sub Open_Report(sender As Object, e As EventArgs) Handles PrintToolStripButton.Click
    Try
        ds_Record = New DataSet
        ds_Record.Tables.Add(dt_Record)
        ds_Record.Tables.Add(dt_ENDP)
        Dim dr As DataRelation = New DataRelation("ENDP", ds_Record.Tables(0).Columns("Endpoint_Tag"), ds_Record.Tables(1).Columns("Computer_Name"))
        ds_Record.Relations.Add(dr)

        frmIT_Report.ShowDialog()

    Catch ex As Exception
        CustExErrorMsg(Me.Name, System.Reflection.MethodBase.GetCurrentMethod().Name, ex.Message)
    Finally
        If Not IsNothing(ds_Record) Then ds_Record.Dispose()
    End Try
End Sub

另一个正在打开的具有 DGV 的表单具有以下代码:

Private Sub frmIT_Report_Load(sender As Object, e As EventArgs) Handles Me.Load
    Try
        With dgvResults
            .RowHeadersWidth = 12
            .DataSource = frmIT_Equip.ds_Record.Tables(0)
        End With
        
        txtResults.Text = CStr(dgvResults.Rows.GetRowCount(DataGridViewElementStates.None))
        
    Catch ex As Exception
        CustExErrorMsg(Name, System.Reflection.MethodBase.GetCurrentMethod().Name, ex.Message)
    End Try
End Sub

当表单打开时,DGV 仅填充 dt_Records 表中的数据,而第二个表中没有任何内容。好的。根据我找到的另一个解决方案,在将关系添加到数据集之后,我添加了以下内容,以查看是否会从第二个表中引入数据:

For Each row As DataRow In ds_Record.Tables(0).Rows
    Row.GetChildRows(dr)
Next

不,这仍然只生成第一个表。然后我找到了另一个解决方案,需要在第一个表的末尾添加一列:

ds_Record.Tables(0).Columns.Add("Build_Date", GetType(String), "Parent.Build_Date")

但这会产生“找不到关系 0”的错误,所以我用另一种解决方案使用 LINQ 加入表格:(我对 LINQ 还不是很熟悉。)

Dim qry = From t1 In ds_Record.Tables(0).AsEnumerable() Join
    t2 In ds_Record.Tables(1).AsEnumerable On
    t1.Item("Endpoint_Tag") Equals t2.Item("Computer_Name")
          Select New With {
                Key .A = t1.Field(Of String)("Endpoint_Tag"),
                .B = t1.Field(Of Integer)("Class_ID"),
                .C = t2.Field(Of String)("Computer_Name"),
                .D = t2.Field(Of String)("Build_Date")}

Dim dt As New DataTable
For Each item In qry.ToList
    dt.Rows.Add(item.A, item.B, item.C, item.D)
Next
ds_Record.Tables.Add(dt)

但这会产生错误“Specified Cast in not allowed”但即使这确实有效,或者宝贵的代码,我也不想在我的代码中强制转换近 70 列 - 这看起来很可笑。

从表面上看,这似乎应该很容易解决。那我错过了什么?

【问题讨论】:

  • 主要是使用 DataTables 而不是匿名或定义的类不能与现代 .Net 类(如 DGV)完全互操作。 OTOH,类也不容易合并许多属性(列)。此外,无论如何,70 列在数据网格中的价值值得怀疑。
  • @NetMage 我不同意 70 列对于 DGV 来说是荒谬的。但是,用户希望在采取该操作之前查看他们将要导出的数据。而且由于“组织”在 SQL 数据管理方面做得很糟糕,因此在各种表中的服务器上都有数据。我只是想把它拼凑起来供我自己使用。

标签: vb.net linq


【解决方案1】:

这是一种扩展方法,可用于将 LINQ 结果从联接转换为新的DataTable。它使用以下扩展方法:

Module QueryExt
    <System.Runtime.CompilerServices.Extension>
    Public Function AsSingleton(Of T As New)(item As T) As IEnumerable(Of T)
        Return New T() { item }
    End Function

    ' ***
    ' *** Type Extensions
    ' ***
    <System.Runtime.CompilerServices.Extension> 
    Public Function GetPropertiesOrFields(t As Type, Optional bf As BindingFlags = BindingFlags.Public Or BindingFlags.Instance) As List(Of MemberInfo)
        Return t.GetMembers(bf).Where(Function(mi) (mi.MemberType = MemberTypes.Field) Or (mi.MemberType = MemberTypes.Property)).ToList()
    End Function

    ' ***
    ' *** MemberInfo Extensions
    ' ***
    <System.Runtime.CompilerServices.Extension>
    Public Function GetMemberType(member As MemberInfo) As Type
        If Typeof member Is FieldInfo Then
            Dim mfi As FieldInfo = member
            Return mfi.FieldType
        ElseIf Typeof member Is PropertyInfo Then
            Dim mpi As PropertyInfo = member
            Return mpi.PropertyType
        ElseIf Typeof member Is EventInfo Then
            Dim mei As EventInfo = member
            Return mei.EventHandlerType
        Else
            Throw New ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", Nameof(member))
        End If
    End Function

    <System.Runtime.CompilerServices.Extension> 
    Public Function GetValue(member As MemberInfo, srcObject As Object) As Object
        If Typeof member Is FieldInfo Then
            Dim mfi As FieldInfo = member
            Return mfi.GetValue(srcObject)
        ElseIf Typeof member Is PropertyInfo Then
            Dim mpi As PropertyInfo = member
            Return mpi.GetValue(srcObject)
        ElseIf Typeof member Is MethodInfo Then
            Dim mi As MethodInfo = member
            Return mi.Invoke(srcObject, Nothing)
        Else
            Throw New ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or MethodInfo", Nameof(member))
        End If
    End Function
    <System.Runtime.CompilerServices.Extension> 
    Public Function GetValue(Of T)(member As MemberInfo, srcObject As Object) As T
        Return DirectCast(member.GetValue(srcObject), T)
    End Function

    ' ***
    ' *** DataTable Extensions
    ' ***
    <System.Runtime.CompilerServices.Extension> 
    Public Function DataColumns(aTable As DataTable) As IEnumerable(Of DataColumn)
        Return aTable.Columns.Cast(Of DataColumn)()
    End Function
    <System.Runtime.CompilerServices.Extension> 
    Public Function ColumnNames(aTable As DataTable) As IEnumerable(Of String)
        Return aTable.DataColumns().Select(Function(dc) dc.ColumnName)
    End Function

    ' Create new DataTable from LINQ join results on DataTable
    ' Expect T to be anonymous object of form new { [DataRow or other] d1, [DataRow or other] d2, ... }
    ' result flattens each object to DataRow with Fields from each dx or a new Field named after dx
    <System.Runtime.CompilerServices.Extension> 
    Public Function FlattenToDataTable(Of T)(items As IEnumerable(Of T)) As DataTable
        Dim res = New DataTable()
        If (items.Any()) Then
            Dim firstRow = items.First()
            Dim memberInfos = GetType(T).GetPropertiesOrFields()
            Dim allDC = memberInfos.SelectMany(Function(mi) If(mi.GetMemberType() = GetType(DataRow), mi.GetValue(Of DataRow)(firstRow).Table.DataColumns(), New DataColumn(mi.Name, mi.GetMemberType()).AsSingleton()))

            For Each dc In allDC
                Dim newColumnName = dc.ColumnName
                If res.ColumnNames().Contains(newColumnName) Then
                    Dim suffixNumber = 1
                    While res.ColumnNames().Contains($"{newColumnName}.{suffixNumber}")
                        suffixNumber += 1
                    End While
                    newColumnName = $"{newColumnName}.{suffixNumber}"
                End If
                res.Columns.Add(New DataColumn(newColumnName, dc.DataType))
            Next

            For Each item In items
                res.Rows.Add(memberInfos.SelectMany(Function(mi) If(mi.GetMemberType() = GetType(DataRow), mi.GetValue(Of DataRow)(item).ItemArray, (New Object() { mi.GetValue(item) }).ToArray())))
            Next
        End If
        Return res
    End Function
    
End Module

通过此扩展方法,您可以按如下方式使用 LINQ:

Dim qry = From t1 In ds_Record.Tables(0).AsEnumerable()
          Join t2 In ds_Record.Tables(1).AsEnumerable On t1.Item("Endpoint_Tag") Equals t2.Item("Computer_Name")
          Select New With { t1, t2 }
Dim dt = qry.FlattenToDataTable()

【讨论】:

  • 我将扩展添加到我的项目中,并更新了 LINQ 查询以匹配。但是,我收到以下错误:"Unable to cast object of type '&lt;SelectManyIterator&gt;d__17``2[System.Reflection.MemberInfo,System.Object]' to type 'System.IConvertible'.Couldn't store &lt;System.Linq.Enumerable+&lt;SelectManyIterator&gt;d__17``2[System.Reflection.MemberInfo,System.Object]&gt; in ID_IT_Asset Column. Expected type is Int32." 在以下行:res.Rows.Add(memberInfos.SelectMany(Function(mi)... 没有足够的空间来添加整行,但这应该告诉你。
  • 我能够让它工作,稍微。我将第一个表 (dt_Records) 更改为我的第三个表 (dt_ITSM)。 DGV“填充”,但它的唯一值是"System.Linq.Enumerable+&lt;SelectManyIterator&gt;d__17'2[System.Reflection.MemberInfo,System.Object]" 在第一列中重复。看起来两个表的所有标题都已加载。我会继续玩它,看看我是否能解决这个问题。如果你碰巧弄明白了,请告诉我。
  • 可能是我从原始 C# 转换为 VB 的问题...我会看看我能不能弄明白。
【解决方案2】:

我能够设法将我的三个表“加入”到一个 DataTable 中。当然,它可能不是最有效的,但它确实有效。

代码首先创建“父”DataTable 的克隆和副本。然后它遍历每个“子”DataTable 的列并添加这些列。 (我必须为每个表添加一个前缀,因为这些表具有相同的名称。)

然后循环遍历新 DataTable 的行,并使用 SELECT 语句捕获数据并将其放入相应的列中。

我相信这也可以封装成一个函数。

    Private Sub Open_Report(sender As Object, e As EventArgs) Handles PrintToolStripButton.Click
    Try
        Dim dt As New DataTable
        dt = dt_Record.Clone
        dt = dt_Record.Copy

        '** ITSM
        For Each col As DataColumn In dt_ITSM.Columns
            dt.Columns.Add(New DataColumn("ITSM_" + col.ColumnName, col.DataType))
        Next
        For Each row As DataRow In dt.Rows
            Dim val As DataRow = dt_ITSM.Select("asset_tag = '" + row.Item("Endpoint_Tag").ToString + "'").FirstOrDefault
            If Not val Is Nothing Then
                For Each col As DataColumn In val.Table.Columns
                    row("ITSM_" + col.ColumnName) = val.Item(col.ColumnName)
                Next
            End If
        Next

        '** Endpoint
        For Each col As DataColumn In dt_ENDP.Columns
            dt.Columns.Add(New DataColumn("ENDP_" + col.ColumnName, col.DataType))
        Next
        For Each row As DataRow In dt.Rows
            Dim val As DataRow = dt_ENDP.Select("Computer_Name = '" + row.Item("Endpoint_Tag").ToString + "'").FirstOrDefault
            If Not val Is Nothing Then
                For Each col As DataColumn In val.Table.Columns
                    row("ENDP_" + col.ColumnName) = val.Item(col.ColumnName)
                Next
            End If
        Next

        ds_Record = New DataSet
        ds_Record.Tables.Add(dt)

        frmIT_Report.ShowDialog()

    Catch ex As Exception
        CustExErrorMsg(Me.Name, System.Reflection.MethodBase.GetCurrentMethod().Name, ex.Message)
    Finally
        If Not IsNothing(ds_Record) Then ds_Record.Dispose()
    End Try
End Sub

【讨论】:

    猜你喜欢
    • 2015-08-06
    • 1970-01-01
    • 2019-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-09
    • 2011-10-10
    • 2020-09-11
    相关资源
    最近更新 更多