【问题标题】:C# BulkCopy, DBF Errors (Timout, & Provider could not determine...)C# BulkCopy、DBF 错误(超时和提供程序无法确定...)
【发布时间】:2012-02-25 00:34:45
【问题描述】:

我编写了一个小型控制台应用程序,它指向一个包含 DBF/FoxPo 文件的文件夹。

然后它根据每个 dbf 表在 SQL 中创建一个表,然后进行批量复制以将数据插入到 SQL 中。它在大多数情况下都运行良好,除了一些障碍..

1) 一些 FoxPro 表包含 5000000+ 条记录,并且在插入完成之前连接到期..

这是我的连接字符串:

<add name="SQL" connectionString="data source=source_source;persist security info=True;user id=DBFToSQL;password=DBFToSQL;Connection Timeout=20000;Max Pool Size=200" providerName="System.Data.SqlClient" />

错误信息: “超时。在操作完成之前超时时间已过或服务器没有响应。”

代码:

using (SqlConnection SQLConn = new SqlConnection(SQLString))
using (OleDbConnection FPConn = new OleDbConnection(FoxString))
{
    ServerConnection srvConn = new Microsoft.SqlServer.Management.Common.ServerConnection(SQLConn);
    try
    {
        FPConn.Open();                       
        string dataString = String.Format("Select * from {0}", tableName);

        using (OleDbCommand Command = new OleDbCommand(dataString, FPConn))
        using (OleDbDataReader Reader = Command.ExecuteReader(CommandBehavior.SequentialAccess))
        {                       
            tbl = new Table(database, tableName, "schema");

            for (int i = 0; i < Reader.FieldCount; i++)
            {                           
                col = new Column(tbl, Reader.GetName(i), ConvertTypeToDataType(Reader.GetFieldType(i)));
                col.Nullable = true;
                tbl.Columns.Add(col);                       
            }

            tbl.Create();                       
            BulkCopy(Reader, tableName);
        }                   
    }
    catch (Exception ex)
    {
       // LogText(ex, @"C:\LoadTable_Errors.txt", tableName);
        throw ex;
    }
    finally
    {
        SQLConn.Close();
        srvConn.Disconnect();
    }
}

private DataType ConvertTypeToDataType(Type type)
{
    switch (type.ToString())
    {
        case "System.Decimal":
            return DataType.Decimal(18, 38);
        case "System.String":
            return DataType.NVarCharMax;
        case "System.Int32":
            return DataType.Int;
        case "System.DateTime":
            return DataType.DateTime;
        case "System.Boolean":
            return DataType.Bit;
        default:
            throw new NotImplementedException("ConvertTypeToDataType Not implemented for type : " + type.ToString());
    }
}

 private void BulkCopy(OleDbDataReader reader, string tableName)
{
    using (SqlConnection SQLConn = new SqlConnection(SQLString))
    {       
        SQLConn.Open();
        SqlBulkCopy bulkCopy = new SqlBulkCopy(SQLConn);

        bulkCopy.DestinationTableName = "schema." + tableName;

        try
        {
            bulkCopy.WriteToServer(reader);         
        }
        catch (Exception ex)
        {           
            //LogText(ex, @"C:\BulkCopy_Errors.txt", tableName);
        }
        finally
        {
            SQLConn.Close();
            reader.Close();
        }
    }
}

我的第二个和第三个错误如下:

我知道问题是什么,但我不太确定如何纠正它们

2) “提供者无法确定 Decimal 值。例如,刚刚创建的行,Decimal 列的默认值不可用,消费者尚未设置新的 Decimal 值。”

3) SqlDateTime 溢出。必须在 1753 年 1 月 1 日凌晨 12:00:00 到 9999 年 12 月 31 日晚上 11:59:59 之间。

我在 google 上找到了一个结果,表明了问题所在:[A]... 和一个可能的解决方法 [B] (但我想将我的十进制值保留为十进制,并将日期保留为日期,如我会根据数据做进一步的计算)

我想做的解决方案

1.) 要么增加连接时间,(但我认为我不能增加它比我有的更多),或者是否可以拆分 OleDbDataReader 的结果并在增量批量插入中进行?

2.)我在想是否可以进行批量复制以忽略有错误的结果,或者将出错的记录记录到 csv 文件或类似程度?

【问题讨论】:

  • 就回答 (1) 而言 - 这本来是我的建议。比如说 1000 次记录爆发。这应该解决这个问题,并希望防止冻结/锁定问题。至于 (2) - 如果缺少默认值,为什么不使用默认值 - 比如说 .MinValue 样式,或者可能 -1 表示十进制,01/01/1980 表示日期。 (这些只是示例 - 可以使用适合您需要的任何值)。
  • 对如何进行增量阅读有任何想法吗?我一直在寻找,但到目前为止还没有运气......

标签: c# bulkinsert smo foxpro sqlbulkcopy


【解决方案1】:

这是我目前在批量复制方法中的尝试,我不适用于大约 90% 的表,但我得到了 OutOfMemory 异常,对于更大的表...我想将读者的数据拆分为较小的部分,无需将其传递到 DataTable 并首先将其存储在内存中(这是较大结果集上 OutOfMemory 异常的原因)

更新

修改了下面的代码,以了解它在我的解决方案中的外观。它不是很漂亮。但它可以工作。我会做一些重构,并再次更新我的答案。

    private void BulkCopy(OleDbDataReader reader, string tableName, Table table)
    {
        Console.WriteLine(tableName + " BulkCopy Started.");
        try
        {
            DataTable tbl = new DataTable();
            List<Type> typeList = new List<Type>();
            foreach (Column col in table.Columns)
            {
                tbl.Columns.Add(col.Name, ConvertDataTypeToType(col.DataType));
                typeList.Add(ConvertDataTypeToType(col.DataType));
            }

            int batch = 1;
            int counter = 0;

            DataRow tblRow = tbl.NewRow();

            while (reader.Read())
            {
                counter++;
                int colcounter = 0;
                foreach (Column col in table.Columns)
                {
                    try
                    {
                        tblRow[colcounter] = reader[colcounter];
                    }
                    catch (Exception)
                    {
                        tblRow[colcounter] = GetDefault(typeList[0]);
                    }
                    colcounter++;
                }

                tbl.LoadDataRow(tblRow.ItemArray, true);

                if (counter == BulkInsertIncrement)
                {
                    Console.WriteLine(tableName + " :: Batch >> " + batch);
                    counter = PerformInsert(tableName, tbl, batch);
                    batch++;
                }
            }

            if (counter > 0)
            {
                Console.WriteLine(tableName + " :: Batch >> " + batch);
                PerformInsert(tableName, tbl, counter);
            }

            tbl = null;
            Console.WriteLine("BulkCopy Success!");
        }
        catch (Exception ex)
        {
            Console.WriteLine("BulkCopy Fail!");
            SharedLogger.Write(ex, @"C:\BulkCopy_Errors.txt", tableName);
            Console.WriteLine(ex.Message);
        }
        finally
        {
            reader.Close();
            reader.Dispose();

        }
        Console.WriteLine(tableName + " BulkCopy Ended.");
        Console.WriteLine("*****");
        Console.WriteLine("");
    }

【讨论】:

  • 我想做的是将创建表的代码重构为自己的方法。然后你可以在我提到的 for 循环之前调用它,这样它只完成一次,然后你的批量复制只处理复制。从技术上讲,您有一种方法可以完成两项工作 - 考虑分离关注点。一种方法做一项工作。
  • 该表用于临时存储目的,因为我无法拆分 OleDbDataReader.. 但感谢您的输入。
【解决方案2】:

所以你在哪里做“for”声明我可能会把它分解成一次拿这么多:

int i = 0;
int MaxCount = 1000;

while (i < Reader.FieldCount)
{
    var tbl = new Table(database, tableName, "schema"); 

    for (int j = i; j < MaxCount; j++) 
    {                            
        col = new Column(tbl, Reader.GetName(j), ConvertTypeToDataType(Reader.GetFieldType(j))); 
        col.Nullable = true; 
        tbl.Columns.Add(col);
        i++;                      
    } 

    tbl.Create();                        
    BulkCopy(Reader, tableName); 
}

因此,“i”跟踪总计数,“j”跟踪增量计数(即一次最大计数),当您创建“批次”时,您创建表并批量复制它。

这看起来像您期望的那样吗?

干杯,
克里斯。

【讨论】:

  • 我明白你要做的事情,这是正确的......尽管如此,你修改的代码部分负责列和表的创建。我需要逐步将数据传递给 sql,这意味着对 BulkCopy 方法的修改。我将发布我当前的尝试作为答案
猜你喜欢
  • 1970-01-01
  • 2022-07-04
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
  • 2021-12-15
  • 2015-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多