我已经编写了一个函数来读取csv文件并相应地对它们进行参数化,因此我有一个函数gettypessql,它首先查询sql表以获取数据类型,因此调整后来插入sql的列。所以我的问题是当我在Jet OLE DB中设置HDR = Yes时,我只获得了F1,F2,F3等列名。为了避免这个问题,我设置了HDR = No并写了一些for循环,但现在我只得到空字符串,实际上是什么问题?这是我的代码:
Private Function GetCSVFile(ByVal file As String, ByVal min As Integer, ByVal max As Integer) As DataTable
Dim ConStr As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & TextBox1.Text & ";Extended Properties=""TEXT;HDR=NO;IMEX=1;FMT=Delimited;CharacterSet=65001"""
Dim conn As New OleDb.OleDbConnection(ConStr)
Dim dt As New DataTable
Dim da As OleDb.OleDbDataAdapter = Nothing
getData = Nothing
Try
Dim CMD As String = "Select * from " & _table & ".csv"
da = New OleDb.OleDbDataAdapter(CMD, conn)
da.Fill(min, max, dt)
getData = New DataTable(_table)
Dim firstRow As DataRow = dt.Rows(0)
For i As Integer = 0 To dt.Columns.Count - 1
Dim columnName As String = firstRow(i).ToString()
Dim newColumn As New DataColumn(columnName, mListOfTypes(i))
getData.Columns.Add(newColumn)
Next
For i As Integer = 1 To dt.Rows.Count - 1
Dim row As DataRow = dt.Rows(i)
Dim newRow As DataRow = getData.NewRow()
For j As Integer = 0 To getData.Columns.Count - 1
If row(j).GetType Is GetType(String) Then
Dim colValue As String = row(j).ToString()
colValue = ChangeEncoding(colValue)
colValue = ParseString(colValue)
colValue = ReplaceChars(colValue)
newRow(j) = colValue
Else
newRow(j) = row(j)
End If
Next
getData.Rows.Add(newRow)
Application.DoEvents()
Next
Catch ex As OleDbException
MessageBox.Show(ex.Message)
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
dt.Dispose()
da.Dispose()
End Try
Return getData
End Function
并获取类型sql,这个没有正确转换,特别是双打
Private Sub GetTypesSQL()
If (mListOfTypes Is Nothing) Then
mListOfTypes = New List(Of Type)()
End If
mListOfTypes.Clear()
Dim dtTabelShema As DataTable = db.GetDataTable("SELECT TOP 0 * FROM " & _table)
Using dtTabelShema
For Each col As DataColumn In dtTabelShema.Columns
mListOfTypes.Add(col.DataType)
Next
End Using
End Sub
答案 0 :(得分:2)
我认为你使它变得比它需要的更复杂。例如,通过创建空DataTable
并从中获取数据类型来获取dbSchema。为什么不使用第一个表而不是从类型创建新表?对于导入的每批行,也不需要反复重建该表。
通常,因为OleDb
会尝试从数据中推断类型,所以似乎没有必要,甚至可能在某些情况下妨碍。此外,您正在重做OleDB所做的一切,并将数据复制到不同的DT。鉴于此,我将跳过OleDB的开销并使用原始数据。
这将使用CSV列名称和数据库中的类型创建目标表。如果CSV与SELECT *
查询中提供的列的顺序不同,则会失败。
以下使用类将csv列映射到db表列,因此代码不依赖于CSV的顺序相同(因为它们可能在外部生成)。我的示例数据CSV 不的顺序相同:
Public Class CSVMapItem
Public Property CSVIndex As Int32
Public Property ColName As String = ""
'optional
Public Property DataType As Type
Public Sub New(ndx As Int32, csvName As String,
dtCols As DataColumnCollection)
CSVIndex = ndx
For Each dc As DataColumn In dtCols
If String.Compare(dc.ColumnName, csvName, True) = 0 Then
ColName = dc.ColumnName
DataType = dc.DataType
Exit For
End If
Next
If String.IsNullOrEmpty(ColName) Then
Throw New ArgumentException("Cannot find column: " & csvName)
End If
End Sub
End Class
解析csv的代码使用CSVHelper
但在这种情况下可以使用TextFieldParser
,因为代码只是将CSV行读入字符串数组。
Dim SQL = String.Format("SELECT * FROM {0} WHERE ID<0", DBTblName)
Dim rowCount As Int32 = 0
Dim totalRows As Int32 = 0
Dim sw As New Stopwatch
sw.Start()
Using dbcon As New MySqlConnection(MySQLConnStr)
Using cmd As New MySqlCommand(SQL, dbcon)
dtSample = New DataTable
dbcon.Open()
' load empty DT, create the insert command
daSample = New MySqlDataAdapter(cmd)
Dim cb = New MySqlCommandBuilder(daSample)
daSample.InsertCommand = cb.GetInsertCommand
dtSample.Load(cmd.ExecuteReader())
' dtSample is not only empty, but has the columns
' we need
Dim csvMap As New List(Of CSVMapItem)
Using sr As New StreamReader(csvfile, False),
parser = New CsvParser(sr)
' col names from CSV
Dim csvNames = parser.Read()
' create a map of CSV index to DT Columnname SEE NOTE
For n As Int32 = 0 To csvNames.Length - 1
csvMap.Add(New CSVMapItem(n, csvNames(n), dtSample.Columns))
Next
' line data read as string
Dim data As String()
data = parser.Read()
Dim dr As DataRow
Do Until data Is Nothing OrElse data.Length = 0
dr = dtSample.NewRow()
For Each item In csvMap
' optional/as needed type conversion
If item.DataType = GetType(Boolean) Then
' "1" wont convert to bool, but (int)1 will
dr(item.ColName) = Convert.ToInt32(data(item.CSVIndex).Trim)
Else
dr(item.ColName) = data(item.CSVIndex).Trim
End If
Next
dtSample.Rows.Add(dr)
rowCount += 1
data = parser.Read()
If rowCount = 50000 OrElse (data Is Nothing OrElse data.Length = 0) Then
totalRows += daSample.Update(dtSample)
' empty the table if there will be more than 100k rows
dtSample.Rows.Clear()
rowCount = 0
End If
Loop
End Using
End Using
End Using
sw.Stop()
Console.WriteLine("Parsed and imported {0} rows in {1}", totalRows,
sw.Elapsed.TotalMinutes)
如果行数很多,处理循环每50K行更新一次DB。它也是一次通过,而不是一次通过OleDB读取N行。 CsvParser
将一次读取一行,因此一次不会有超过50,001行的数据。
如If item.DataType = GetType(Boolean) Then
所示,可能会有特殊情况需要处理类型转换。读入为“1”的布尔列不能直接传递给布尔列,因此将其转换为可以的整数。可能会有其他转换,例如时髦的约会。
处理250,001行的时间:3.7分钟。需要将这些字符串转换应用于每个字符串列的应用程序将花费更长的时间。我很确定使用CsvReader
中的CSVHelper
,您可以将这些应用作为解析类型的一部分。
潜在的灾难等待发生,因为这是一个通用的进口商/洗涤器。
For i As Integer = 0 To dt.Columns.Count - 1
Dim columnName As String = firstRow(i).ToString()
Dim newColumn As New DataColumn(columnName, mListOfTypes(i))
getData.Columns.Add(newColumn)
Next
问题和自我答案都使用CSV中的列名和目标表上SELECT *
查询的数据类型构建新表。因此,它假定CSV列与SELECT *
将返回它们的顺序相同,并且所有CSV将始终使用与表相同的名称。
上面的答案略微好一点,因为它根据名称找到并匹配。
更强大的解决方案是编写一个小应用程序应用程序,用户将数据库列名称映射到CSV索引。将结果保存到List(Of CSVMapItem)
并序列化。可能有一整套这些保存到磁盘。然后,不是根据航位推算创建地图,而只是将用户所需的反序列化为上面代码中的csvMap
。