C# BulkCopy, ошибки DBF (Timout и Provider не могут определить)

Я написал небольшое консольное приложение, указывающее на папку, содержащую файлы DBF/FoxPo.

Затем он создает таблицу в SQL на основе каждой таблицы dbf, а затем выполняет массовое копирование для вставки данных в 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. Должно быть между 01.01.1753, 00:00:00 и 31.12.9999, 23:59:59.

Я нашел в Google результат, в котором указывалось, в чем проблема: "nofollow noreferrer">[A]... и возможный обходной путь [B] (но я бы хотел, чтобы мои десятичные значения были десятичными, а даты - датой, так как я буду выполнять дальнейшие вычисления с данными)

Что я хочу сделать в качестве решения

1.) Либо увеличьте время соединения (но я не думаю, что смогу увеличить его больше, чем у меня есть), или, в качестве альтернативы, возможно ли разделить результаты OleDbDataReader и выполнить инкрементную массовую вставку?

2.) Я подумал, возможно ли массовое копирование, чтобы игнорировать результаты с ошибками, или записать записи, которые выводят ошибки, в файл csv или что-то в этом роде?


person Rohan Büchner    schedule 02.02.2012    source источник
comment
Что касается ответа (1) - это было бы моим предложением. Сделайте это, скажем, 1000 пакетов записей. Это должно решить эту проблему и, надеюсь, предотвратить проблемы с зависанием/блокировкой. Что касается (2) - почему бы не использовать значения по умолчанию, если они отсутствуют - например, стиль .MinValue или, возможно, -1 для десятичного числа и 01/01/1980 для даты. (Это всего лишь примеры — можно использовать любые значения, соответствующие вашим потребностям).   -  person Bertie    schedule 02.02.2012
comment
любая идея о том, как сделать инкрементное чтение? Я искал, но пока безрезультатно...   -  person Rohan Büchner    schedule 02.02.2012


Ответы (2)


Итак, где вы делаете оператор «для», я, вероятно, разбил бы его, чтобы взять так много за раз:

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» отслеживает добавочный подсчет (т. е. максимальное количество за один раз), и когда вы создали свой «пакет», вы создаете таблицу и массовое копирование.

Это похоже на то, что вы ожидаете?

С уважением,
Крис.

person Bertie    schedule 02.02.2012
comment
Я понимаю, что вы пытаетесь сделать, и это правильно... хотя измененный вами раздел кода отвечает за создание столбца и таблицы. Мне нужно постепенно передавать данные в sql, что будет означать модификацию метода BulkCopy. Я опубликую свою текущую попытку в качестве ответа - person Rohan Büchner; 03.02.2012

Это моя текущая попытка метода массового копирования, я не работаю примерно с 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("");
    }
person Rohan Büchner    schedule 03.02.2012
comment
У меня возникло бы искушение реорганизовать код, создающий таблицу, в отдельный метод. Затем вы можете вызвать это перед циклом for, который я упомянул, чтобы он выполнялся только один раз, тогда ваша массовая копия будет иметь дело только с копированием. Технически у вас есть метод, выполняющий две работы — подумайте о разделении задач. Один метод выполняет одну работу. - person Bertie; 03.02.2012
comment
Таблица предназначена для временного хранения, так как я не могу разделить OleDbDataReader. Но спасибо за вклад. - person Rohan Büchner; 03.02.2012