我正在 vb.net (.Net Framework >= 4.5) 中编写一个应用程序,该应用程序通过 Web 服务(作为数据表对象)从数据库检索数据,并通过以下方式将其解析为特定格式的 Excel 文件(不幸的是):互操作库。
应用程序运行良好,但最近我开始收到此 OutOfMemory 错误:
HRESULT 异常:0x8007000E (E_OUTOFMEMORY)
当我使用以下方法将数据从数据表解析为 excel.range 对象(rng)时,会发生错误:
rng.value = ConvertDataTableTo2DArray(DT)
函数 ConvertDataTableTo2DArray 接受一个数据表对象并返回一个包含信息的二维数组。我这样做已经有一段时间了,因为这似乎是进行此类操作的最快方法(根据我在网上的研究)。
Public Shared Function ConvertDataTableTo2DArray(dataTable As DataTable) As Object(,)
Dim rows As Integer = dataTable.Rows.Count
Dim columns As Integer = dataTable.Columns.Count
Dim dataArray(rows - 1, columns - 1) As Object
For i As Integer = 0 To rows - 1
For j As Integer = 0 To columns - 1
dataArray(i, j) = dataTable.Rows(i)(j)
Next
Next
Return dataArray
End Function
咨询到处发布的其他问题,我发现 Excel.Range 对象的大小有限,因此我解决该问题的最初方法是将数据表按 x 行数的批次进行划分,并再次声明一个新范围每批次。这根本不能解决问题,所以要么我没有清除以前正确使用的数据,要么错误的原因并不完全是我想象的那样。
一些附加信息:
我的代码(不幸的是名称是西班牙语):
Public Shared Sub CopiarDataTableAExcel(dataTable As DataTable, excelHoja As Excel.Worksheet, inicioCelda As String)
' Obtener el rango de inicio en base a la celda especificada
Dim rangoInicio As Excel.Range = excelHoja.Range(inicioCelda)
' Obtener el rango final en base al tamaño de la DataTable
Dim rangoFin As Excel.Range = excelHoja.Cells(rangoInicio.Row + dataTable.Rows.Count - 1, rangoInicio.Column + dataTable.Columns.Count - 1)
' Obtener el rango completo que abarca desde el inicio hasta el fin
Dim rangoCompleto As Excel.Range = excelHoja.Range(rangoInicio, rangoFin)
Try
' Copiar los datos de la DataTable al rango completo en Excel
rangoCompleto.Value = ConvertDataTableTo2DArray(dataTable)
Catch ex As Exception
If ex.Message.Contains("E_OUTOFMEMORY") Then
Dim filas_por_particion As Int16 = 20
Dim particiones As Int16 = Math.Ceiling(dataTable.Rows.Count / filas_por_particion)
For prt = 1 To particiones
Dim num_filas As Integer = filas_por_particion
If prt = particiones Then
num_filas = dataTable.Rows.Count - (prt * filas_por_particion) ' numero de filas menos las filas ya escritas
End If
'Dim nuevo_rango_inicio As Excel.Range = excelHoja.Range("A" & ((prt - 1) * num_filas + 2).ToString)
Dim nuevo_rango_inicio As Excel.Range = excelHoja.Cells((prt - 1) * num_filas + 2, 1)
Dim nuevo_rango_fin As Excel.Range = excelHoja.Cells(nuevo_rango_inicio.Row + num_filas, nuevo_rango_inicio.Column + dataTable.Columns.Count - 1)
Dim nuevo_rango As Excel.Range = excelHoja.Range(nuevo_rango_inicio, nuevo_rango_fin)
Dim auxDt As DataTable = dataTable.Clone
For fila = (prt - 1) * num_filas To prt * num_filas - 1
auxDt.ImportRow(dataTable.Rows(fila))
Next
nuevo_rango.Value = ConvertDataTableTo2DArray(auxDt)
Next
Else
MsgBox("Se ha producido el siguiente error: " & Chr(13) & ex.Message)
End If
End Try
End Sub
在代码的第一部分到
rangoCompleto.Value = ConvertDataTableTo2DArray(dataTable)
,你可以找到我最初的方法。在catch语句中,我引入了这个仍然失败的“批量”副本。根据我的理解,nuevo_rango* 变量的每个声明都应该清除以前使用的内存,因此新的 excel.range nuevo_rango 应该使用只有 x (num_filas) 行和 22 列的新范围。在调试时,我检查了我的批处理数据是否达到了预期的大小,这就是为什么我不理解 OutOfMemory 错误。
更新:
按照@djv的建议和他提供的链接中的信息,我尝试了这个:
Public Shared Sub CopiarDataTableAExcel(dataTable As DataTable, excelHoja As Excel.Worksheet, inicioCelda As String)
' Obtener el rango de inicio en base a la celda especificada
Dim rangoInicio As Excel.Range = excelHoja.Range(inicioCelda)
' Obtener el rango final en base al tamaño de la DataTable
Dim rangoFin As Excel.Range = excelHoja.Cells(rangoInicio.Row + dataTable.Rows.Count - 1, rangoInicio.Column + dataTable.Columns.Count - 1)
' Obtener el rango completo que abarca desde el inicio hasta el fin
Dim rangoCompleto As Excel.Range = excelHoja.Range(rangoInicio, rangoFin)
Try
' Copiar los datos de la DataTable al rango completo en Excel
rangoCompleto.Value = ConvertDataTableTo2DArray(dataTable)
Catch ex As Exception
If ex.Message.Contains("E_OUTOFMEMORY") Then
Dim filas_por_particion As Int16 = 100
Dim particiones As Int16 = Math.Ceiling(dataTable.Rows.Count / filas_por_particion)
For prt = 1 To particiones
Dim num_filas As Integer = filas_por_particion
If prt = particiones Then
num_filas = (prt * filas_por_particion) - dataTable.Rows.Count ' numero de filas menos las filas ya escritas
End If
DT2Excel(excelHoja, num_filas, prt, dataTable)
GC.Collect()
GC.WaitForPendingFinalizers()
Next
Else
MsgBox("Se ha producido el siguiente error: " & Chr(13) & ex.Message)
End If
End Try
End Sub
Public Shared Function DT2Excel(exl As Excel.Worksheet, num_filas As Int16, num_iter As Int16, dt As DataTable) As Boolean
Try
Dim rng_inicio As New Tuple(Of Int16, Int16)((num_iter - 1) * num_filas + 2, 1)
Dim nuevo_rango_inicio As Excel.Range = exl.Cells(rng_inicio.Item1, rng_inicio.Item2)
Dim rng_fin As New Tuple(Of Int16, Int16)(nuevo_rango_inicio.Row + num_filas, nuevo_rango_inicio.Column + dt.Columns.Count - 1)
Dim nuevo_rango_fin As Excel.Range = exl.Cells(rng_fin.Item1, rng_fin.Item2)
Dim nuevo_rango As Excel.Range = exl.Range(nuevo_rango_inicio, nuevo_rango_fin)
Dim auxDt As DataTable = dt.Clone
For fila = (num_iter - 1) * num_filas To num_iter * num_filas - 1
auxDt.ImportRow(dt.Rows(fila))
Next
nuevo_rango.Value = ConvertDataTableTo2DArray(auxDt)
nuevo_rango = Nothing
Return True
Catch ex As Exception
Return False
End Try
End Function
它不再抛出错误,但在第 104 行之后仍然没有复制。我什至使用另一个函数中的范围封装了该部分,以避免出现注意到的调试错误,但我觉得只有当我封装all Excel 时,这才有效对象,这会违背函数的目的。
看起来 Excel.range 对象仍然没有释放内存...我想我可以实例化工作簿并在每次迭代中打开文件,但这违背了快速执行操作的目的。
尝试释放函数内的范围
Public Shared Function DT2Excel(exl As Excel.Worksheet, num_filas As Int16, num_iter As Int16, dt As DataTable) As Boolean
Dim nuevo_rango_inicio As Excel.Range
Dim nuevo_rango_fin As Excel.Range
Dim nuevo_rango As Excel.Range
Try
Dim rng_inicio As New Tuple(Of Int16, Int16)((num_iter - 1) * num_filas + 2, 1)
nuevo_rango_inicio = exl.Cells(rng_inicio.Item1, rng_inicio.Item2)
Dim rng_fin As New Tuple(Of Int16, Int16)(nuevo_rango_inicio.Row + num_filas, nuevo_rango_inicio.Column + dt.Columns.Count - 1)
nuevo_rango_fin = exl.Cells(rng_fin.Item1, rng_fin.Item2)
nuevo_rango = exl.Range(nuevo_rango_inicio, nuevo_rango_fin)
Dim auxDt As DataTable = dt.Clone
For fila = (num_iter - 1) * num_filas To num_iter * num_filas - 1
auxDt.ImportRow(dt.Rows(fila))
Next
nuevo_rango.Value = ConvertDataTableTo2DArray(auxDt)
Return True
Catch ex As Exception
Return False
Finally
nuevo_rango = Nothing
nuevo_rango_inicio = Nothing
nuevo_rango_fin = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
End Try
End Function
这可能并不重要。退出函数应该释放引用,GC 应该正确处理它,但值得一试。
也在 For 循环中的方法
CopiarDataTableAExcel
中执行相同操作。使用 Finally
块清除引用和 GC。不过,有点奇怪的是,您似乎在内存不足异常之后在异常处理程序中执行实际工作。
我也有类似的情况和同样的错误。事实证明,某些数据元素以
=
字符开头,这导致了内存不足错误。在将它们写入 Excel 之前,我通过在这些字符串的开头添加撇号 '
来修复此问题。例如。 =sample_text
变为 '=sample_text
。这样,Excel 不会尝试将它们解释为公式,而是解释为普通文本字符串。