68

尝试从 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)

我明白错误在说什么,但我怎样才能获得更多信息,例如发生这种情况的行/字段?数据表由第 3 方填充,最多可包含 200 列和最多 10k 行。返回的列取决于发送给第 3 方的请求。所有数据列都是字符串类型。我的数据库中的列并不都是 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)

有没有人有任何想法要解决?或任何想法来获得更多信息?

4

10 回答 10

92

对于那些偶然发现这个问题并收到关于 nvarchar 而不是金钱的类似错误消息的人:

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

这可能是由太短的列引起的。

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

资源

于 2014-03-10T23:25:57.777 回答
42

请使用 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);
                }
            }
        }
    }
}  
于 2014-05-08T19:57:28.327 回答
34

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

当您使用 Sql Bulk Copy 时,它会尝试将您的输入数据直接与服务器上的数据对齐。因此,它获取服务器表并执行类似于以下的 SQL 语句:

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

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

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

Sql Bulk Insert 必须确定列映射将是额外的工作和开销。因此,它允许您选择...确保您的代码和 SQL 表列的顺序相同,或者明确声明按列名对齐。

因此,如果您的问题是列未对齐,这可能是导致此错误的主要原因,那么此答案适合您。

TLDR

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

这将采用您现有的 DataTable,您尝试将其插入到您创建的 BulkCopy 对象中,它只会将名称显式映射到名称。当然,如果出于某种原因,您决定将 DataTable 列命名为不同于 SQL Server 列的名称……那就由您决定。

于 2018-06-22T20:31:40.023 回答
24

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

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

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

//--- 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());
}

进行上述调整后,一切都插入没有问题。

于 2013-08-09T14:36:07.850 回答
6

确保您在具有获取设置属性的实体类中添加的列值也以与目标表中存在的顺序相同。

于 2016-09-22T05:46:37.750 回答
5

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

于 2018-03-21T15:56:48.127 回答
3

不会是每个人的解决方案,但它适合我:

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

当我将我的 DataTable 调整为具有 id 时,副本运行良好。

在我的场景中,Id 列对于拥有主键并不是很重要,因此我从目标目标表中删除了此列,并且 SqlBulkCopy 可以正常工作。

于 2019-11-06T19:45:13.053 回答
0

我的问题是列映射而不是值。我正在从开发系统中提取,创建目标表,批量复制内容,从产品系统中提取,调整目标表并批量复制内容,因此 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);
于 2021-05-18T18:03:33.223 回答
0

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

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

于 2022-01-11T19:38:55.950 回答
-3

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

喜欢

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

你的分隔符是'|' 但数据有一个分隔符';' . 所以为此你得到了错误。

于 2015-05-28T07:38:13.893 回答