【发布时间】:2021-01-18 17:58:15
【问题描述】:
我的问题是我需要将 90.000+ 行/143 列从 DataGridView(从 MySQL 数据库填充)导出到 Excel。无论我做什么,我总是在 45k-60k 行之后出现“System.Out.Of.Memory”异常,具体取决于解决方案。我知道可能会有诸如“为什么需要这么多行”之类的问题,我会回答“不幸的是,这是需要的”。我搜索了有关我的问题的论坛,但没有找到任何可行的解决方案。我尝试将 StreamWriter 转换为 CSV,分块处理数据(下面的解决方案),还使用多个 Excel 或 CSV 文件,但没有任何帮助。每次执行期间,当我尝试使用较少的行数时,RAM 使用量都会增长,并且在成功导出后不会释放。我不知道在成功执行后何时以及是否释放 RAM。
测试机器有 8 GB 的 RAM,并且使用的是 Windows 10。不幸的是,我无法使用 MySQL 服务器的资源在那里处理 Excel 导出,然后输出文件以与用户共享,所以我需要使用客户端机器。
以下是我最新的不起作用的解决方案,其中数据从 DGV 读取并以块的形式写入 Excel。改变块的大小并不会减少内存消耗,如果我把它变小(比如 500 到 2000),唯一的影响就是导出速度越来越慢。
Imports Excel = Microsoft.Office.Interop.Excel
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If DataGridView1.Rows.Count > 0 Then
Dim filename As String = ""
Dim SV As SaveFileDialog = New SaveFileDialog()
SV.FileName = "Worst_cells"
SV.Filter = "xlsx files (*.xlsx)|*.xlsx|All files (*.*)|*.*"
SV.FilterIndex = 1
SV.RestoreDirectory = True
Dim result As DialogResult = SV.ShowDialog()
If result = DialogResult.OK Then
filename = SV.FileName
Dim XCELAPP As Microsoft.Office.Interop.Excel.Application = Nothing
Dim XWORKBOOK As Microsoft.Office.Interop.Excel.Workbook = Nothing
Dim XSHEET As Microsoft.Office.Interop.Excel.Worksheet = Nothing
Dim misValue As Object = System.Reflection.Missing.Value
XCELAPP = New Excel.Application()
XWORKBOOK = XCELAPP.Workbooks.Add(misValue)
XCELAPP.DisplayAlerts = False
XCELAPP.Visible = False
XSHEET = XWORKBOOK.ActiveSheet
XSHEET.Range("B1").ColumnWidth = 11
For Each column As DataGridViewColumn In DataGridView1.Columns
XSHEET.Cells(1, column.Index + 1) = column.HeaderText
Next
Dim rowCnt As Integer = DataGridView1.Rows.Count
Dim colCnt As Integer = DataGridView1.Columns.Count
Dim batchSize As Integer = 10000
Dim currentRow As Integer = 0
Dim valueObjArray As Object(,) = New Object(batchSize - 1, colCnt - 1) {}
While currentRow < rowCnt
Dim rowIndex As Integer = 0
While rowIndex < batchSize AndAlso currentRow + rowIndex < rowCnt
For colIndex As Integer = 0 To colCnt - 1
valueObjArray(rowIndex, colIndex) = DataGridView1(colIndex, currentRow + rowIndex).Value
Next
rowIndex += 1
End While
Dim colName As String = ColumnLetter(colCnt)
If (currentRow + batchSize + 1) < rowCnt Then
XSHEET.Range("A" + (currentRow + 2).ToString(), colName + (currentRow + batchSize + 1).ToString()).Value2 = valueObjArray
Else
XSHEET.Range("A" + (currentRow + 2).ToString(), colName + (rowCnt + 1).ToString()).Value2 = valueObjArray
End If
XWORKBOOK.SaveAs(filename)
currentRow += batchSize
End While
XCELAPP.DisplayAlerts = True
XWORKBOOK.Close(False)
XCELAPP.Quit()
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(XSHEET)
System.Runtime.InteropServices.Marshal.ReleaseComObject(XWORKBOOK)
System.Runtime.InteropServices.Marshal.ReleaseComObject(XCELAPP)
Catch
End Try
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End If
End If
End Sub
【问题讨论】:
-
您是否尝试过 Excel 的 Oledb 提供程序?网格中怎么可能需要 90,000 行。用户不能查看 90,000 行。
-
我还没有检查过 Oledb。你认为值得检查吗?关于您如何需要 90k 行的问题:假设您有一个包含 90k+ 个单元的移动网络,并且网络存在一个重大问题,有成千上万的客户投诉。在这种情况下,工程师需要能够识别最大的单元贡献者,还需要能够对单元列表进行后处理,以发现流量变化、KPI 降级等。
-
不确定是否有区别,但您没有释放任何
Range对象。通常它会类似于Range r = ...; r.Value2 = ...; Marshal.ReleaseComObject(r);应避免在代码中使用双点。 -
DGV 是否绑定到DataTable?如果是这样,请考虑从
DataRow.ItemArray中提取值,而不是通过 DGV Cell.Value 来避免复制数据。 -
您不回应提供更多信息的请求。你还在寻找解决这个问题的方法吗?我怀疑问题是由于迭代 DGV 行导致它们变得不共享并消耗大量内存。这可以通过在行集合上运行一个空的
For Each循环来验证。