在 vb.net 中获取列名 Jet OLE DB
get column names Jet OLE DB in vb.net
我写了一个函数来读取 csv 文件并相应地对其进行参数化,因此我有一个函数 gettypessql 首先查询 sql table 以获取数据类型和因此要调整稍后插入 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
我认为你把它弄得比需要的更复杂了。例如,您通过创建一个空 DataTable
并从中获取数据类型来获取 dbSchema。为什么不直接使用第一个 table 而不是从类型中创建一个新的 table? table 也不需要为每批导入的行一遍又一遍地重建。
通常,由于 OleDb
会尝试从数据中推断类型,这似乎是不必要的,在某些情况下甚至会妨碍。此外,您正在重做 OleDB 所做的一切并将数据复制到不同的 DT。鉴于此,我将跳过 OleDB 强加的开销并使用原始数据。
这会使用数据库中的 CSV 列名称和类型创建目标 table。如果 CSV 与 SELECT *
查询中提供的列顺序不同,它将失败。
以下使用 class 将 csv 列映射到 db table 列,因此代码不依赖于顺序相同的 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 行更新一次数据库。它还一次性完成,而不是一次通过 OleDB 读取 N 行。 CsvParser
将一次读取一行,因此手头的数据一次不应超过 50,001 行。
如 If item.DataType = GetType(Boolean) Then
所示,可能需要处理类型转换的特殊情况。读入为“1”的布尔列不能直接传递给布尔列,所以它被转换为可以的整数。可能还有其他转换,例如时髦的日期。
处理 250,001 行的时间:3.7 分钟。需要将这些字符串转换应用于每个字符串列的应用程序将花费更长的时间。我很确定在 CSVHelper
中使用 CsvReader
你可以将它们作为解析类型的一部分应用。
有一个潜在的灾难等待发生,因为这是一个通用的 importer/scrubber。
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 的列名和来自目标 table 的 SELECT *
查询的数据类型构建新的 table。因此,它假定 CSV 列的顺序与 SELECT *
将 return 它们的顺序相同,并且所有 CSV 将始终使用与 table 相同的名称。
上面的答案稍微好一些,因为它根据名称查找和匹配。
一个更强大的解决方案是编写一个小实用程序,用户可以在其中将数据库列名称映射到 CSV 索引。将结果保存到 List(Of CSVMapItem)
并序列化。可以将这些的整个集合保存到磁盘中。然后,不是基于航位推算创建地图,而是将用户所需的反序列化为上面代码中的 csvMap
。
我写了一个函数来读取 csv 文件并相应地对其进行参数化,因此我有一个函数 gettypessql 首先查询 sql table 以获取数据类型和因此要调整稍后插入 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
我认为你把它弄得比需要的更复杂了。例如,您通过创建一个空 DataTable
并从中获取数据类型来获取 dbSchema。为什么不直接使用第一个 table 而不是从类型中创建一个新的 table? table 也不需要为每批导入的行一遍又一遍地重建。
通常,由于 OleDb
会尝试从数据中推断类型,这似乎是不必要的,在某些情况下甚至会妨碍。此外,您正在重做 OleDB 所做的一切并将数据复制到不同的 DT。鉴于此,我将跳过 OleDB 强加的开销并使用原始数据。
这会使用数据库中的 CSV 列名称和类型创建目标 table。如果 CSV 与 SELECT *
查询中提供的列顺序不同,它将失败。
以下使用 class 将 csv 列映射到 db table 列,因此代码不依赖于顺序相同的 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 行更新一次数据库。它还一次性完成,而不是一次通过 OleDB 读取 N 行。 CsvParser
将一次读取一行,因此手头的数据一次不应超过 50,001 行。
如 If item.DataType = GetType(Boolean) Then
所示,可能需要处理类型转换的特殊情况。读入为“1”的布尔列不能直接传递给布尔列,所以它被转换为可以的整数。可能还有其他转换,例如时髦的日期。
处理 250,001 行的时间:3.7 分钟。需要将这些字符串转换应用于每个字符串列的应用程序将花费更长的时间。我很确定在 CSVHelper
中使用 CsvReader
你可以将它们作为解析类型的一部分应用。
有一个潜在的灾难等待发生,因为这是一个通用的 importer/scrubber。
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 的列名和来自目标 table 的 SELECT *
查询的数据类型构建新的 table。因此,它假定 CSV 列的顺序与 SELECT *
将 return 它们的顺序相同,并且所有 CSV 将始终使用与 table 相同的名称。
上面的答案稍微好一些,因为它根据名称查找和匹配。
一个更强大的解决方案是编写一个小实用程序,用户可以在其中将数据库列名称映射到 CSV 索引。将结果保存到 List(Of CSVMapItem)
并序列化。可以将这些的整个集合保存到磁盘中。然后,不是基于航位推算创建地图,而是将用户所需的反序列化为上面代码中的 csvMap
。