是否有更有效的方法来读取此DataTable,以便将其写入CSV文件?

时间:2012-01-03 14:55:03

标签: .net vb.net file-io csv

我正在查看我编写的旧网络应用程序,它需要大约一个小时才能从DataTable读取4500条记录,因此可以将它们写入CSV文件。我觉得必须有一些方法来改善这一点。

很少有事情需要注意:

  • DataTable包含... 376列

    至少,我认为Excel的NL列转换为。我现在只是查看了列数,并且不知道有那么多。我们的软件供应商还没有意识到这个过程的动态sql语句的价值,所以每个软件“升级”只是不断添加更多列而不是仅选择所需的列。

  • 我无法更改生成数据的SQL语句

  • 根据数据类型,数据需要以特定格式进行格式化

  • 数据确实包含特殊字符,例如逗号

  • 缓慢的部分是读取数据。从SQL服务器获取数据并将其写入CSV很快。

这是代码。原谅这个烂摊子,当我不知道我在做什么以及什么时候我还在VB工作时我把它写回来

Function ReadDataTableForCSV(dt as DataTable)

    Dim sb = New StringBuilder()
    Dim dataTypes As New Dictionary(Of String, Integer)

    ' Header Row
    For i as Integer = 0 to dt.Columns.Count - 1
        Dim col as DataColumn = dt.Columns(i)

        Dim t = col.DataType
        If t is GetType(Boolean) Then
            dataTypes.Add(i, 1)
        Else If t is GetType(Decimal) Then
            dataTypes.Add(i, 2)
        Else
            dataTypes.Add(i, 3)
        End If

        sb.Append(String.Format("""{0}""", col.ColumnName))
        sb.Append(Iif(i = dt.Columns.Count - 1, vbLf, ","))

    Next

    ' Items
    For Each row as DataRow in dt.Rows
        For i As Integer = 0 To dt.Columns.Count - 1
            Select dataTypes(i)
                Case 1
                sb.Append(String.Format("""{0}""", CInt(row(i))))
            Case 2
                sb.Append(String.Format("""{0}""", FormatNumber(row(i), 2, , , 0)))
            Case 3
                sb.Append(String.Format("""{0}""", row(i)))
            End Select

            sb.Append(Iif(i = dt.Columns.Count - 1, vbLf, ","))
      Next
    Next

End Function

修改:删除与问题无关的代码

3 个答案:

答案 0 :(得分:2)

以下是我将如何重写它:

  1. 预先分配stringbuilder内存。

  2. 将数据类型从字典更改为字节数组,仅使用值1和2;值3现在为0,这将是数组中项目的默认值。

  3. 使用列中的Ordinal属性而不是单独的索引。

  4. 简化项目和行分隔符循环内的评估。

  5. 使用Decimal.ToString而不是FormatNumber。

  6. 删除iifs(这些可能是由编译器优化的,但我从VB早期开始仍然对它们持怀疑态度)

  7. 以下是代码:

    Function ReadDataTableForCSV(dt As DataTable)
    
        Dim sb As New StringBuilder(100000000)
        Dim dataTypes As Byte()
    
        ReDim dataTypes(dt.Columns.Count - 1)
    
        ' Header Row
        For Each col As DataColumn In dt.Columns
            If sb.Length <> 0 Then
                sb.Append(",")
            End If
    
            Select Case col.DataType.ToString
                Case "System.Boolean"
                    dataTypes(col.Ordinal) = 1
    
                Case "System.Decimal"
                    dataTypes(col.Ordinal) = 2
    
                    ' Everything else defaults to 0
    
            End Select
    
            sb.Append("""").Append(col.ColumnName).Append("""")
        Next
    
        sb.AppendLine()
        ' or  sb.Append(vbLf)
    
        ' Items
        For Each row As DataRow In dt.Rows
            For i As Integer = 0 To dt.Columns.Count - 1
                If i <> 0 Then
                    sb.Append(",")
                End If
    
                sb.Append("""")
                Select Case dataTypes(i)
                    Case 1
                        If CBool(row(i)) Then
                            sb.Append("1")
                        Else
                            sb.Append("0")
                        End If
    
                    Case 2
                        sb.Append(CDec(row(i)).ToString("F"))
    
                    Case Else
                        sb.Append(row(i))
    
                End Select
    
                sb.Append("""")
            Next
    
            sb.AppendLine()
            ' or  sb.Append(vbLf)
        Next
    
    End Function
    

答案 1 :(得分:0)

结帐Filehelpers。它非常快,处理引用的字段,并简化了读取和写入分隔和固定宽度文件。

可以在Codeproject上找到演练。

如果您使用类似Dapper的ORM将数据库中的记录读入强类型类,那么您可以将ORM中的强类型类数组(将使用DelimitedRecord和FieldConverter属性修饰)传递给Filehelpers Engine。

Sourceforge和Filehelpers网站上的下载链接有点旧。它有效,但我建议从Sourceforge中提取最新资源。

修改

没有什么能成为单一的表现。也许这是一个小点击的集合?试一试。

  • 不是填充和枚举DataTable,而是直接枚举DataReader。少拳击。
  • 如果您使用的是最新版本的VB.NET,请使用三元IF而不是IIF。它的类型安全。
  • 更少String.Formats。
  • 更少的字典查找。我成像的类型检查比字典查找更快。也许有人可以对此说话?
  • 较少的隐式类型转换。

这是黑暗重写的镜头。 :)

Function ReadDataTableForCSV(dr As SqlDataReader) As String
    Dim fieldCount As Integer = dr.FieldCount
    Dim sb = New StringBuilder()

    ' Header Row
    For i As Integer = 0 To fieldCount - 1
        sb.AppendFormat("""{0}""", dr.GetName(i))
        sb.Append(If(i = fieldCount - 1, vbLf, ","))
    Next

    ' Items
    While dr.Read()
        For i As Integer = 0 To fieldCount - 1
            Dim t As Type = dr.GetFieldType(i)

            sb.Append("""") 'quoted cell
            If t Is GetType(Boolean) Then
                sb.Append(If(dr.GetBoolean(i), "1", "0"))
            ElseIf t Is GetType(Decimal) Then
                sb.Append(dr.GetDecimal(i).ToString("#.##"))
            Else
                sb.Append(dr(i))
            End If
            sb.Append("""") 'quoted cell

            sb.Append(If(i = fieldCount - 1, vbLf, ","))
        Next
    End While

    Return sb.ToString()
End Function

答案 2 :(得分:0)

如果获取数据表是瓶颈,并且您无法更改sql语句并且结果以数据表形式返回,则无法完成任何操作。

如果你可以控制结果的返回方式,那么返回一个不可数的。其中T是POCO或IDataRecord,使用延迟执行。这将使内存消耗保持在较低水平,因为您只会在使用时加载记录。

读取数据可能如下所示

while(reader.Reader())
{
    yield return reader; // or reader.ConvertRecordToObject<T>(); //extension method
}

然后你可以foreach将结果写到csvfile

foreach(var record in GetEnumerationOfRecords())
{
     WriteToCsv(record);
}

这仍然留下了sql语句的问题,这很可能导致大部分性能问题,但你无法做任何事情。