Есть ли более эффективный способ чтения этой таблицы данных, чтобы я мог записать ее в файл CSV? - PullRequest
1 голос
/ 03 января 2012

Я смотрю на старое веб-приложение, которое я написал, и на чтение 4500 записей из DataTable уходит около часа, чтобы оно могло записать их в файл CSV.Я чувствую, что должен быть какой-то способ улучшить это.

Несколько замечаний:

  • Таблица данных содержит ... 376 столбцов

    Впо крайней мере, я думаю, что это то, что преобразует столбец NL в Excel.Я просто посмотрел количество столбцов и понятия не имел, что их так много.Наш поставщик программного обеспечения еще не осознал значение динамических 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 ]

2 голосов
/ 04 января 2012

Вот как бы я переписал это:

  1. Выделите память строителя строк заранее.

  2. Изменить типы данных из словаря в байтовый массив и использовать только значения 1 и 2; значение 3 теперь будет 0, что будет значением по умолчанию для элементов в массиве.

  3. Используйте свойство Ordinal из столбца, а не отдельный индекс.

  4. Оптимизировать вычисления внутри цикла для разделителей элементов и строк.

  5. Использовать Decimal.ToString вместо FormatNumber.

  6. Удалить iif (вероятно, они оптимизированы компилятором, но я все еще подозреваю их с первых дней VB)

Вот код:

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
0 голосов
/ 04 января 2012

, если получение данных является узким местом, и вы не можете изменить оператор SQL, а результаты возвращаются как датируемые, мало что можно сделать.

если вы можете контролировать, как возвращаются результаты, тогда возвращайте ienumerable. где 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, который, скорее всего, вызывает большую часть проблемы с производительностью, но вы ничего не можете с этим поделать.

0 голосов
/ 03 января 2012

Оформить Filehelpers . Это очень быстро, обрабатывает поля в кавычках и упрощает чтение и запись файлов с разделителями и фиксированной ширины.

Пошаговое руководство можно найти на Codeproject .

Если вы читаете записи из базы данных в строго типизированные классы, используя ORM, например Dapper , тогда вы можете передать массив строго типизированных классов из ORM, который будет украшен атрибутами DelimitedRecord и FieldConverter, движку Filehelpers.

Ссылка для скачивания на Sourceforge и на сайте Filehelpers устарела. Это работает, но я бы порекомендовал получить последнюю версию Sourceforge.

EDIT

Ничто так не выделяется, как единственный хит производительности. Может быть, это коллекция маленьких хитов? Дайте этому шанс.

  • Вместо того, чтобы заполнять и перечислять DataTable, перечисляйте DataReader напрямую. Меньше бокса.
  • Если вы используете последнюю версию VB.NET, используйте троичный IF, а не IIF. Это безопасный тип.
  • Меньше строк. Форматы.
  • Меньше словарных поисков. Я представляю себе проверку типов быстрее, чем поиск по словарю. Может быть, кто-то может говорить об этом?
  • Меньше неявных преобразований типов.

Это выстрел в темноте переписать. :)

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...