【问题标题】:Sequence or Batch items DataGridView序列或批处理项 DataGridView
【发布时间】:2016-07-26 18:09:53
【问题描述】:

我有 94​​0000 行的大型 DataGridView...哎呀,通过解析 csv 文件填充,DataGridView 有一个名为 Sequence 的列,编号为 1 到 940000。我试图做的是重新编号要溢出的序列DataGridView 中的行数为 1 到 7000 的序列。重新排序序列列的最有效方法是什么?

Using reader As New Microsoft.VisualBasic.FileIO.TextFieldParser(fileName)
        reader.TextFieldType = FileIO.FieldType.Delimited
        reader.SetDelimiters(",")
        Dim currentRow As String()
        Dim serial As String
        Dim sequence As Integer = 0
        Dim RollId As String

        'pbUploadFile.Maximum = serialAmmount / quantityBreak
        pbUploadFile.Maximum = serialAmmount
        pbUploadFile.Step = 1
        pbUploadFile.Value = 0

        For i = 1 To serialAmmount / quantityBreak
            For j = 1 To quantityBreak
                Try
                    currentRow = reader.ReadFields()
                    serial = currentRow(0).ToString
                    sequence += 1
                    EnterDataIntoDatabase(serial, sequence, nextRollNumber, ddSelectPartNumber.Text)
                    pbUploadFile.Increment(1)
                Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
                    MsgBox("Code " & ex.Message & "is not valid and will be skipped check csv file")
                End Try
            Next j

            sqlCmd = New SqlClient.SqlCommand("SELECT * FROM serials WHERE Sequence=@sequence AND RollNo=@rollNo ", sqlCon)
            sqlCmd.CommandType = CommandType.Text
            sqlCmd.Parameters.AddWithValue("@sequence", 1)
            sqlCmd.Parameters.AddWithValue("@rollNo", nextRollNumber)
            sqlCon.Open()
            Dim readRollId As SqlClient.SqlDataReader = sqlCmd.ExecuteReader()
            If readRollId.Read() Then
                RollId = readRollId.Item("Code")
            End If
            sqlCon.Close()


            UpdateAvailableRolls(ddSelectPartNumber.Text, nextRollNumber, RollId)
            nextRollNumber += 1
            UpdateRollNo(nextRollNumber)
            sequence = 0
            'pbUploadFile.Increment(1)
        Next i
        SaveFile()
    End Using

【问题讨论】:

  • 有效的方法是将数据存储在列表或数据表中,然后使用数据而不是用户的视图。您是否在填充时设置了序列?你可以在加载数据时这样做吗? (标题与其余部分不匹配)。
  • 嗨 Plutonix 序列来自 csv 文件,该文件有两列序列号和序列号从 1 到 940000。我试图分解为 1 到 7000。我与 DataGridView 一起使用所以我可以更容易地直观地看到我在做什么。
  • 使用数据容器不会阻止在表单上显示数据。文件中包含字符串以外的数据的可能性是 6-5-pickem,但直接填充 DGV,您最终会得到所有字符串。当您遍历文件时,您可以计算行数并根据需要将序列重置为 1。你知道你最终会得到 130 多行具有相同序列号的行吗?
  • 感谢 Plutonix,是的,相同的序列号是我想要的行为,我正在尝试将这些 1-940000 序列号分类为编号为 1-7000 的批次。我以前从未处理过如此大的文件。
  • 数据的最终目的地是什么?意思是在它被排序/批处理之后会发生什么,也许用户批准了它?它是发送到数据库、返回 CSV 还是什么?

标签: .net vb.net datagridview


【解决方案1】:

通常最好考虑数据的使用方式,以及在决定具体如何做某事以及使用什么工具来做某事时如何使用。没有人是对的|快|做大多数事情的有效方法。

也就是说,做事有一些不好的方法。使用DataGridView 作为数据容器似乎是不明智的(尽管我实际上在代码中看不到任何与 DGV 相关的内容)。 A)数据没有自动进入它的方式 - 你必须编写代码才能做到这一点,并且 2)数据没有自动方式进入其他地方 - 你必须编写代码来循环它并且把数据捞回来。然后是所有数据可能存储为字符串的问题。

看起来还有更多的事情不仅仅是批量处理项目。下面将从CSV导入行,处理它们并将它们写回DB(我使用的是MySql,但概念是相同的)。


首先,TextFieldParser 是一个非常方便的工具,但它的一个主要缺点是它只返回字符串。如果 CSV 中包含价格、日期、布尔值等,则该类型将丢失。在很多情况下,CSVHelper 会是更好的选择。

在这种情况下,由于数据是发往数据库的,所以我会使用 OleDB 将 CSV 读入 DataTable,对其进行批处理,然后将其发送到数据库。

使用 OleDB 导入数据

Schema.INI

OleDb 包含一个可用于解析 CSV 的文本文件驱动程序。它可以根据前几行的上下文“猜测”数据类型,但您也可以定义它们。在 CSV 所在的文件夹/目录中,创建一个名为 Schema.INI 的新文本文件。像这样定义 CSV 和列:

[Capitals.Csv]
ColNameHeader=真
格式=CSV 分隔
文本分隔符=
DecimalSymbol=.
货币符号=$
Col1="Country" 文本宽度 254
Col2="Capital City" 文字宽度 254
Col3="人口" 单
Col4="Rank" 整数
col5="国庆"日期

  • 您可以在一个文件中包含多个 csv 定义,每个定义都以 [...] 开头
  • [...] 将是 CSV 的名称
  • 如果 CSV 有标题行,则可以将其用作列名
  • 如果列也用引号引起来(“Like this”、“in”、“the csv”),请使用TextDelimiter="
  • 每个Col#= 条目定义数据类型并且可以覆盖名称。这允许您将 CSV 中名为“Foo”的列“映射”到数据库中名为“Bar”的列。
  • 可以指定文件中使用的小数点和货币符号以及代码页等其他选项。

连接字符串

要使用的连接字符串是:

ACEImportStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='{0}';Extended Properties='TEXT'"

Data Source 将是 CSV 和 Schema.INI 都存在的文件夹,并且“TEXT”元素告诉它使用文本驱动程序。使用文件夹名称填空:

ACEImportConnStr = String.Format(ACEImportConnStr, "C:\Temp")

OLEDB.12 有时可能很挑剔,如果您有问题,请改用Microsoft.Jet.OLEDB.4.0 作为 Provider。

现在,要加载数据,只需从 CSV 文件名中选择(无文件夹):

Dim sSQL = "SELECT * FROM RandomOle.CSV"
...
Dim daSrc = New OleDbDataAdapter(sSQL, OleCSVConnstr)
rowsLoaded = daSrc.Fill(dtSample)

DataAdapter 将在几秒钟内读取 Schema 中的定义并将 CSV 加载到数据表中。处理其他任务还有很多工作要做,但这就是概念。

Dim sSQL = "SELECT * FROM YOUR_CSVFILE_NAME.CSV"
Dim sw As New Stopwatch

Dim rowsLoaded As Int32
Dim rowsUpdated As Int32

sw.Start()
ACEImportConnStr = String.Format(ACEImportConnStr, "C:\Temp")

' create Destination MySQL conn, Src and Dest dataadapters,
' and a command builder (because I am lazy...and fallible)
Using mysqlCon As New MySqlConnection(MySQLConnStr),
    daSrc As New OleDbDataAdapter(sSQL, ACEImportConnStr),
    daDest As New MySqlDataAdapter("SELECT * FROM Sample", mysqlCon),
    cb As New MySqlCommandBuilder(daDest)

    ' important!
    daSrc.AcceptChangesDuringFill = False

    dtSample = New DataTable
    rowsLoaded = daSrc.Fill(dtSample)

    ' csv lacks an ID column - add it
    Dim dc As New DataColumn("Id", GetType(Int32))
    dc.DefaultValue = 1
    dtSample.Columns.Add(dc)
    dc.SetOrdinal(0)

    ' MY csv also lacks a BATCH column
    dc = New DataColumn("Batch", GetType(Int32))
    dc.DefaultValue = 1
    dtSample.Columns.Add(dc)
    dc.SetOrdinal(1)

    ' set the batch number
    ' each 5k rows == a batch
    Dim batch As Int32 = 1
    Dim counter As Int32 = 1
    For Each dr As DataRow In dtSample.Rows
        dr("Batch") = batch
        counter += 1
        If counter > 5000 Then
            counter = 0
            batch += 1
        End If
    Next

    ' now save the data to MySQL
    mysqlCon.Open()
    ' inserting 250k rows takes a while,
    ' use a transaction
    Using t As MySqlTransaction = mysqlCon.BeginTransaction
        rowsUpdated = daDest.Update(dtSample)
        t.Commit()
    End Using

End Using

' show the IMPORT in a dgv
dgv1.DataSource = dtSample
dgv1.Columns("Id").Visible = False

' report
sw.Stop()
Console.WriteLine(sw.ElapsedMilliseconds)

原理很简单:由于数据是绑定到一个DB的,所以尽快把数据拿到DataTable。这里的诀窍是涉及到 2 个 DB 提供程序:OleDB 读取 csv 和 MySql 进行保存。

  • 通常当 DataAdapter 填充 DataTable 时,所有行都设置为 UnchangedAcceptChangesDuringFill = False 将状态设置为 Added,以便 MySql 适配器可以插入这些行。
  • CommandBuilder 构建要在 SELECT 命令中使用的 INSERT SQL。
  • 我不知道 serials-rollno 查询在做什么,但我会在导入过程循环中运行查询。如果您需要设置的某些值取决于数据库中的值,请将它们加载到另一个 DT 并从那里查询它们。有一些 DataTable 扩展方法可以很容易地找到行。
  • 同样,我不知道EnterDataIntoDatabase是做什么的,但你应该努力处理和准备DataTable中所有导入的数据,然后一次更新。

您似乎有更多的事情要做,而不仅仅是对一堆行进行批处理或排序。上面的代码可以在 1.2 分钟内导入 250k 行,分配批号,并在 MySql 中插入 250k 新行(几乎每秒 3500 行)。


如果批处理/排序器类似于 CSV 中的每 X 行,您可能一次只能加载 7000 行,设置值,保存该批处理,然后加载接下来的 7k 行。这将限制一次加载的行数并减少应用程序使用的内存。我不确定它是否适用。

参考:

【讨论】:

  • 令人惊叹的 Plutonix 代码帮助我极大地改进了它以适应我的 csv 文件,现在我目前能够处理 994000 个序列号并在大约 12 秒内将它们批量插入到 sql 中。谢谢你的帮助,我欠你一六杯啤酒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-01
  • 2021-11-08
  • 1970-01-01
  • 2010-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多