【问题标题】:SqlBulkCopy - The given value of type String from the data source cannot be converted to type money of the specified target columnSqlBulkCopy - 来自数据源的 String 类型的给定值无法转换为指定目标列的货币类型
【发布时间】:2013-08-10 23:47:49
【问题描述】:

我在尝试从 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 方的请求。所有 datatable 列都是字符串类型。我的数据库中的列并不都是 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)

有人有什么想法可以解决吗?或者有什么想法可以获取更多信息?

【问题讨论】:

  • 查看文档,您可以使用NotifyAfter 属性,该属性将在复制 N 行时引发事件。它可以帮助您确定发生默认行的批次。
  • 您的CleanDecimal 方法是否尝试验证数据,或者只是去除无效字符?您是否尝试过使用Decimal.TryParse() 来查看数据的格式是否正确?试一试,看看它是否会出现有问题的值。

标签: c# datatable sqlbulkcopy


【解决方案1】:

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

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

这可能是由于列太短造成的。

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

Source

【讨论】:

    【解决方案2】:

    请使用 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);
                    }
                }
            }
        }
    }  
    

    【讨论】:

    • 如果您查看问题的代码示例,正在设置 ColumnMappings。这与映射列无关,而与进入这些列的数据格式无关。
    • 虽然他们说 SqlBulkCopy 自动映射这些列,但事实是您确实需要指定它们,否则您将收到这些类型的错误。这至少对我有用。
    • 我以前使用过自动映射,但我在一个包含近 120 列的数据集中遇到了与 bigint(而不是 varchar)相同的问题。只需显式映射列即可解决它。去图吧。
    • 这对我有用。我正在使用另一个答案的 ToDataTable() 扩展方法,但它失败了。它失败的原因是数据库中的列顺序与类中的属性顺序不同。
    • 在我手动指定列映射之前,我也遇到了随机错误。在我的情况下,因为我已经手动设置了批量复制表列,所以我可以自动创建映射: foreach (dataColumn column in bulkCopyTable.Columns) { sqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping( column.ColumnName, column.ColumnName )); }
    【解决方案3】:

    由于我不相信"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 列不同的名称......那就由您来决定。

    【讨论】:

    • 那个!非常感谢。这解释了我一直遇到的很多问题。
    • 我遇到了类似的异常,甚至将我的类属性与表对齐也没有解决它。很好的解决方案!
    • DataTable.Columns 没有 Cast 方法
    • @JWiley 你必须是 LINQ 用户才能看到 Cast 扩展。要包含 LINQ 功能,请在文件顶部键入 using System.Linq;。或者,写出.Columns.Cast&lt;... 调用,得到错误,将光标放在错误上,然后使用Ctrl+. 控制周期快捷方式(默认情况下)。这应该包括所需的基础库。
    【解决方案4】:

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

    进行上述调整后,所有内容都可以正常插入。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 为什么这被否决了?由于列顺序错误,我遇到了这个异常的问题。
      【解决方案6】:

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

      【讨论】:

      • 这只是评论质量答案
      【解决方案7】:

      不是每个人都可以解决,但它适合我:

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

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

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

          喜欢

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

          您的分隔符是 '|',但数据有一个分隔符 ';'。 因此,为此您会收到错误消息。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-01-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-08-27
            相关资源
            最近更新 更多