SqlBulkCopy ——数据源中 String 类型的给定值不能转换为指定目标列的类型 money

在尝试从 DataTable 执行 SqlBulkCopy 时遇到此异常。

Error Message: The given value of type String from the data source cannot be converted to type money of the specified target column.
Target Site: System.Object ConvertValue(System.Object, System.Data.SqlClient._SqlMetaData, Boolean, Boolean ByRef, Boolean ByRef)

我知道错误在说什么,但是我如何才能获得更多信息,比如正在发生这种情况的行/字段?数据表由第三方填充,最多可以包含200列和10k 行。返回的列取决于发送给第三方的请求。所有的 < em > 可资料的 列都是字符串类型的。数据库中的列并不都是 varchar,因此,在执行插入之前,我使用以下代码格式化数据表值(删除了非重要代码) :

//--- create lists to hold the special data type columns
List<DataColumn> IntColumns = new List<DataColumn>();
List<DataColumn> DecimalColumns = new List<DataColumn>();
List<DataColumn> BoolColumns = new List<DataColumn>();
List<DataColumn> DateColumns = new List<DataColumn>();


foreach (DataColumn Column in dtData.Columns)
{
//--- find the field map that tells the system where to put this piece of data from the 3rd party
FieldMap ColumnMap = AllFieldMaps.Find(a => a.SourceFieldID.ToLower() == Column.ColumnName.ToLower());


//--- get the datatype for this field in our system
Type FieldDataType = Nullable.GetUnderlyingType(DestinationType.Property(ColumnMap.DestinationFieldName).PropertyType);


//--- find the field data type and add to respective list
switch (Type.GetTypeCode(FieldDataType))
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64: { IntColumns.Add(Column); break; }
case TypeCode.Boolean: { BoolColumns.Add(Column); break; }
case TypeCode.Double:
case TypeCode.Decimal: { DecimalColumns.Add(Column); break; }
case TypeCode.DateTime: { DateColumns.Add(Column); break; }
}


//--- add the mapping for the column on the BulkCopy object
BulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(Column.ColumnName, ColumnMap.DestinationFieldName));
}


//--- loop through all rows and convert the values to data types that match our database's data type for that field
foreach (DataRow dr in dtData.Rows)
{
//--- convert int values
foreach (DataColumn IntCol in IntColumns)
dr[IntCol] = Helpers.CleanNum(dr[IntCol].ToString());


//--- convert decimal values
foreach (DataColumn DecCol in DecimalColumns)
dr[DecCol] = Helpers.CleanDecimal(dr[DecCol].ToString());


//--- convert bool values
foreach (DataColumn BoolCol in BoolColumns)
dr[BoolCol] = Helpers.ConvertStringToBool(dr[BoolCol].ToString());


//--- convert date values
foreach (DataColumn DateCol in DateColumns)
dr[DateCol] = dr[DateCol].ToString().Replace("T", " ");
}


try
{
//--- do bulk insert
BulkCopy.WriteToServer(dtData);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();


//--- handles error
//--- this is where I need to find the row & column having an issue
}

此代码应格式化其目标字段的所有值。在出现这个错误的情况下,清除该错误的十进制函数将删除任何不是0-9或。(小数点)。引发错误的字段在数据库中将为空。

第2级异常有以下错误:

Error Message: Failed to convert parameter value from a String to a Decimal.
Target Site: System.Object CoerceValue(System.Object, System.Data.SqlClient.MetaType, Boolean ByRef, Boolean ByRef, Boolean)

第3级异常有这个错误:

Error Message: Input string was not in a correct format
Target Site: Void StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)

有人有什么好主意吗? 或者有什么好主意来获取更多信息吗?

172882 次浏览

@ Corey-它只是简单地删除了所有无效的字符。然而,你的评论让我想到了答案。

问题是数据库中的许多字段都是可空的。使用 SqlBulkCopy 时,不会将空字符串作为空值插入。因此,在我的字段不是 varchar (bit、 int、 decal、 datetime 等)的情况下,它试图插入一个空字符串,这对于该数据类型显然是无效的。

解决方案是修改循环,在循环中验证这个值(对于非字符串的每个数据类型重复)

//--- convert decimal values
foreach (DataColumn DecCol in DecimalColumns)
{
if(string.IsNullOrEmpty(dr[DecCol].ToString()))
dr[DecCol] = null; //--- this had to be set to null, not empty
else
dr[DecCol] = Helpers.CleanDecimal(dr[DecCol].ToString());
}

在进行上述调整之后,所有插入都没有问题。

对于偶然发现这个问题并且得到类似的关于 nvarchar 而不是金钱的错误信息的人:

无法将数据源中 String 类型的给定值转换为指定目标列的 nvarchar 类型。

这可能是由于专栏文章太短造成的。

例如,如果您的列定义为 nvarchar(20),并且您有一个40个字符的字符串,您可能会得到这个错误。

来源

请使用 SqlBulkCopyColumnMapping。

例如:

private void SaveFileToDatabase(string filePath)
{
string strConnection = System.Configuration.ConfigurationManager.ConnectionStrings["MHMRA_TexMedEvsConnectionString"].ConnectionString.ToString();


String excelConnString = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0\"", filePath);
//Create Connection to Excel work book
using (OleDbConnection excelConnection = new OleDbConnection(excelConnString))
{
//Create OleDbCommand to fetch data from Excel
using (OleDbCommand cmd = new OleDbCommand("Select * from [Crosswalk$]", excelConnection))
{
excelConnection.Open();
using (OleDbDataReader dReader = cmd.ExecuteReader())
{
using (SqlBulkCopy sqlBulk = new SqlBulkCopy(strConnection))
{
//Give your Destination table name
sqlBulk.DestinationTableName = "PaySrcCrosswalk";


// this is a simpler alternative to explicit column mappings, if the column names are the same on both sides and data types match
foreach(DataColumn column in dt.Columns) {
s.ColumnMappings.Add(new SqlBulkCopyColumnMapping(column.ColumnName, column.ColumnName));
}
                   

sqlBulk.WriteToServer(dReader);
}
}
}
}
}

检查您正在写入服务器的数据。可能数据有未使用的分隔符。

喜欢

045|2272575|0.000|0.000|2013-10-07
045|2272585|0.000|0.000;2013-10-07

您的分隔符是 '|',但数据有一个分隔符 ';'。 所以你得到的是错误。

确保在实体类中添加的列值具有 get set 属性,其顺序与目标表中的顺序相同。

还有一个问题,当你尝试映射一个字符串长度的列时, 例如,你必须映射到 TK_NO nvarchar(50) 与目标字段的长度相同。

因为我不相信 "Please use..." plus some random code that is unrelated to the question是一个好的答案,但我相信精神是正确的,我决定正确地回答这个问题。

使用 Sql 大容量复制时,它会尝试将输入数据与服务器上的数据直接对齐。因此,它使用 Server 表并执行类似于下面这样的 SQL 语句:

INSERT INTO [schema].[table] (col1, col2, col3) VALUES

因此,如果给它 Columns 1、3和2,即使名称可能匹配(例如: col1、 col3、 col2)。它将像这样插入:

INSERT INTO [schema].[table] (col1, col2, col3) VALUES
('col1', 'col3', 'col2')

对于 Sql 大容量插入来说,确定列映射将是额外的工作和开销。因此,它允许您选择... 要么确保您的代码和 SQL 表列的顺序相同,要么显式声明按列名对齐。

因此,如果您的问题是列的错误对齐(这可能是导致此错误的主要原因) ,那么这个答案是为您准备的。

TLDR

using System.Data;
//...
myDataTable.Columns.Cast<DataColumn>().ToList().ForEach(x =>
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(x.ColumnName, x.ColumnName)));

这将采用现有的 DataTable (您试图将其插入到已创建的 BulkCopy 对象中) ,它只是显式地将名称映射到名称。当然,如果出于某种原因,您决定将 DataTable 列命名为与 SQLServer 列不同的列... ... 那是您的责任。

不是每个人都能解决的,但对我来说:

所以,我偶然发现了这个问题。我似乎遇到的问题是当我的 DataTable 没有 ID 列,但是目标目标有一个带有主键的列时。

当我将我的 DataTable 改编为一个 id 时,副本工作得非常完美。

在我的场景中,Id 列对于拥有主键非常重要,所以我从目标目标表中删除了这个列,SqlBulkCopy 工作正常。

我的问题是列映射而不是值。我从 dev 系统中进行了提取,创建了目标表,批量复制了内容,从 prod 系统中进行了提取,调整了目标表,并批量复制了内容,这样来自2个批量副本的列顺序就不匹配了

// explicitly setting the column mapping even though the source & destination column names
// are the same as the column orders will affect the bulk copy giving data conversion errors
foreach (DataColumn column in p_dataTable.Columns)
{
bulkCopy.ColumnMappings.Add(
new()
{
SourceColumn = column.ColumnName,
DestinationColumn = column.ColumnName
}
);
}


bulkCopy.WriteToServer(p_dataTable);

简短回答: 将类型从 nvarchar (size)更改为 nvarchar (Max) 它是字符串长度大小的问题

注意: 以上所有的建议都让我写下了这个简短的答案

我“偶尔”也会犯同样的错误。注意: 列映射完全正确,这就是为什么代码在大多数情况下都能正常工作。

我发现根情况是字符串长度问题。目标表列的数据类型为 nvarchar (255)——其中发送的值的长度超过了字符串中的255个字符。

通过增加数据库中的列长度修正了这个问题。

遗憾的是,msg 不能告诉您是哪一列表导致了这个错误。你必须手动猜测/计算出来。