通常最好考虑如何使用数据以及在决定如何做某事以及使用什么工具来完成某件事时如何使用。没有人对|快|做大多数事情的有效方法。
也就是说,有一些不好的做事方式。用一个DataGridView
作为数据容器似乎不明智(尽管我实际上在代码中看不到与 DGV 相关的任何内容)。 A)没有自动方式让数据进入其中 - 你必须编写代码来做到这一点,2)没有自动方式让数据进入其他地方 - 你必须编写代码来循环它并且将数据捞出来。然后是所有数据都可能存储为字符串的问题。
看起来还有更多的事情发生,而不仅仅是批量处理物品。下面将从 CSV 导入行,处理它们并将它们写回数据库(我使用的是 MySql,但概念是相同的)。
首先,TextFieldParser
是一个非常方便的工具,但它有一个主要缺点,即它只返回字符串。如果 CSV 中包含价格、日期、布尔值等,则该类型会丢失。在许多情况下,CSVHelper 会是更好的选择。
在本例中,由于数据的目的地是数据库,因此我将使用 OleDB 将 CSV 读入DataTable
,批处理,然后发送到数据库。
使用 OleDB 导入数据
架构.INI
OleDb 包含一个可用于解析 CSV 的文本文件驱动程序。它可以根据前几行的上下文“猜测”数据类型,但您也可以定义它们。在 CSV 所在的文件夹/目录中,创建一个名为的新文本文件Schema.INI
。像这样定义 CSV 和列:
[大写字母.Csv]
ColNameHeader=True
格式=CSVDelimited
文本分隔符=
小数符号=.
货币符号=$
Col1=“国家/地区”文本宽度 254
Col2=“首都”文本宽度 254
Col3=“人口”单
Col4=“排名”整数
Col5=“国庆节”日期
- 您可以在一个文件中包含多个 csv 定义,每个定义都以
[...]
- The
[...]
是 CSV 的名称
- 如果 CSV 有标题行,则可以使用这些标题行作为列名称
- 如果列也用引号括起来(“Like this”、“in”、“the csv”),请使用
TextDelimiter="
- Each
Col#=
条目定义数据类型并可以覆盖名称。这允许您将 CSV 中名为“Foo”的列“映射”到数据库中名为“Bar”的列。
- 可以指定其他选项,例如小数点和货币符号以及文件中使用的代码页。
连接字符串
要使用的连接字符串是:
ACEImportStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source='{0}';Extended Properties='TEXT'"
The Data Source
将是 CSV 和 Schema.INI 都存在的文件夹,并且“TEXT”元素告诉它使用文本驱动程序。使用文件夹名称填写空白:
ACEImportConnStr = String.Format(ACEImportConnStr, "C:\Temp")
OLEDB.12 有时可能很挑剔,如果有问题,请使用Microsoft.Jet.OLEDB.4.0
改为提供者。
现在,要加载数据,只需从 CSV 文件名中选择(无文件夹):
Dim sSQL = "SELECT * FROM RandomOle.CSV"
...
Dim daSrc = New OleDbDataAdapter(sSQL, OleCSVConnstr)
rowsLoaded = daSrc.Fill(dtSample)
The DataAdapter
将读取定义的架构,并在几秒钟内将 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)
原理很简单:由于数据绑定到数据库,因此将数据放入DataTable
尽快。这里的技巧是,涉及 2 个 DB Provider:OleDB 用于读取 csv,MySql 用于保存。
- 通常当 DataAdapter 填充
DataTable
所有行都设置为Unchanged
. AcceptChangesDuringFill = False
将状态设置为Added
以便 MySql 适配器可以插入这些行。
- CommandBuilder 构建要从 SELECT 命令使用的 INSERT SQL。
- 我不知道该 serials-rollno 查询在做什么,但我会not在导入过程循环内运行查询。如果您需要设置的某些值取决于数据库中的值,请将它们加载到另一个 DT 中并从那里查询它们。有一些 DataTable 扩展方法可以轻松查找行。
- 同样,我也不知道什么
EnterDataIntoDatabase
确实如此,但您应该努力处理和准备中的所有导入数据DataTable
,然后一次性全部更新。
您似乎要做的不仅仅是对一堆行进行批处理或排序。上面的代码可以在 1.2 分钟内导入 250k 行、分配批号以及将 250k 新行插入 MySql(几乎每秒 3500 行)。
如果批处理/排序器类似于 CSV 中按顺序排列的每 X 行,您可能一次只能加载 7000 行,设置值,保存该批处理,然后加载接下来的 7000 行。这将限制任一时间加载的行数并减少应用程序使用的内存。我不确定它是否适用。
参考:
- 架构.ini 文件 https://msdn.microsoft.com/en-us/library/ms709353(v=vs.85).aspx on MSDN