我正在将 csv 文件中的记录批量复制到 sql 表中。sql 表的列是 varchar,列是真实数据类型(基于我们给出的 csv 属性)
假设前 7 列是 varchar(100) 的外键,其余 80+ 列是 Real 数据类型。
在大容量复制期间,我使用了 Out-DataTable 函数,因为显然这是大容量复制的最有效方式(尤其是我们的文件包含 1000 条记录)。
但是,我收到以下错误:
Exception calling "WriteToServer" with "1" argument(s): "The given value of type String from the data source cannot be converted to type real of the specified target column."
现在我希望错误可以准确地指定哪一列,但根据我的研究,我发现这可能与被假定为所有列的字符串类型的数据类型有关。
使用以下内容进行验证:$column.DataType
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
所以问题是:我如何告诉数据表允许前 7 列是字符串,但其余的列是真正的数据类型?
这是代码:
function Get-Type
{
param($type)
$types = @(
'System.Boolean',
'System.Byte[]',
'System.Byte',
'System.Char',
'System.Datetime',
'System.Decimal',
'System.Double',
'System.Guid',
'System.Int16',
'System.Int32',
'System.Int64',
'System.Single',
'System.UInt16',
'System.UInt32',
'System.UInt64')
if ( $types -contains $type ) {
Write-Output "$type"
}
else {
Write-Output 'System.String'
}
} #Get-Type
function Out-DataTable
{
[CmdletBinding()]
param([Parameter(Position=0, Mandatory=$true, ValueFromPipeline = $true)] [PSObject[]]$InputObject)
Begin
{
$dt = new-object Data.datatable
$First = $true
}
Process
{
foreach ($object in $InputObject)
{
$DR = $DT.NewRow()
foreach($property in $object.PsObject.get_properties())
{
if ($first)
{
$Col = new-object Data.DataColumn
$Col.ColumnName = $property.Name.ToString()
if ($property.value)
{
if ($property.value -isnot [System.DBNull]) {
$Col.DataType = [System.Type]::GetType("$(Get-Type $property.TypeNameOfValue)")
}
}
$DT.Columns.Add($Col)
}
if ($property.Gettype().IsArray) {
$DR.Item($property.Name) =$property.value | ConvertTo-XML -AS String -NoTypeInformation -Depth 1
}
else {
$DR.Item($property.Name) = $property.value
}
}
$DT.Rows.Add($DR)
$First = $false
}
}
End
{
Write-Output @(,($dt))
}
} #Out-DataTable
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = $connectionstring
$SqlConnection.Open()
$CSVDataTable = Import-Csv $csvFile | Out-DataTable
# Build the sqlbulkcopy connection, and set the timeout to infinite
$sqlBulkCopy = New-Object ("Data.SqlClient.SqlBulkCopy") -ArgumentList $SqlConnection
$sqlBulkCopy.DestinationTableName = "$schemaName.[$csvFileBaseName]"
$sqlBulkCopy.bulkcopyTimeout = 0
$sqlBulkCopy.batchsize = 50000
$sqlBulkCopy.DestinationTableName = "$schemaName.[$csvFileBaseName]"
#This mapping helps to make sure that the columns match exactly because BulkCopy depends on indexes not column names by default.
#However, with the DataTable, the correct mappings seems to be already taken care of, but putting this here regardless, because why not?
#Better safe than sorry, right? ;)
#https://stackoverflow.com/a/50995201/8397835
foreach ($column in $CSVDataTable.Columns) { $sqlBulkCopy.ColumnMappings.Add($column.ColumnName, $column.ColumnName) > $null }
$sqlBulkCopy.WriteToServer($CSVDataTable)
# Clean Up
$sqlBulkCopy.Close(); $sqlBulkCopy.Dispose()
$CSVDataTable.Dispose()
# Sometimes the Garbage Collector takes too long to clear the huge datatable.
[System.GC]::Collect()
也许是这样的?
伪代码:
foreach ($column in $CSVDataTable.Columns) {
$sqlBulkCopy.ColumnMappings.Add(
if($DestinationTableName.Column.type -eq 'Real') {
$column.type() = 'Real'
}
$column.ColumnName, $column.ColumnName
) > $null
}