Массовая вставка SQL в C# без вставки значений

Я полностью новичок в C#, поэтому я уверен, что получу много комментариев о том, как отформатирован мой код - я приветствую их. Пожалуйста, не стесняйтесь давать любые советы или конструктивную критику, которые вы можете иметь по пути.

Я создаю очень простое приложение Windows Form, которое в конечном итоге должно брать данные из файла Excel различного размера, возможно, несколько раз в день, и вставлять их в таблицу в SQL Server 2005. После этого хранимая процедура в базе данных выполняет различные задачи обновления и вставки в зависимости от значений, вставленных в эту таблицу.

По этой причине я решил использовать метод массовой вставки SQL, так как не могу знать, вставит ли пользователь только 10 строк - или 10 000 - при любом данном выполнении.

Функция, которую я использую, выглядит следующим образом:

public void BulkImportFromExcel(string excelFilePath)
{
    excelApp = new Excel.Application();
    excelBook = excelApp.Workbooks.Open(excelFilePath);
    excelSheet = excelBook.Worksheets.get_Item(sheetName);
    excelRange = excelSheet.UsedRange;
    excelBook.Close(0);
    try
    {
        using (SqlConnection sqlConn = new SqlConnection())
        {
            sqlConn.ConnectionString =
            "Data Source=" + serverName + ";" +
            "Initial Catalog=" + dbName + ";" +
            "User id=" + dbUserName + ";" +
            "Password=" + dbPassword + ";";
            using (OleDbConnection excelConn = new OleDbConnection())
            {
                excelQuery = "SELECT InvLakNo FROM [" + sheetName + "$]";
                excelConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + excelFilePath + ";Extended Properties='Excel 8.0;HDR=Yes'";
                excelConn.Open();
                using (OleDbCommand oleDBCmd = new OleDbCommand(excelQuery, excelConn))
                {
                    OleDbDataReader dataReader = oleDBCmd.ExecuteReader();
                    using (SqlBulkCopy bulkImport = new SqlBulkCopy(sqlConn.ConnectionString))
                    {
                        bulkImport.DestinationTableName = sqlTable;
                        SqlBulkCopyColumnMapping InvLakNo = new SqlBulkCopyColumnMapping("InvLakNo", "InvLakNo");
                        bulkImport.ColumnMappings.Add(InvLakNo);
                        sqlQuery = "IF OBJECT_ID('ImportFromExcel') IS NOT NULL BEGIN SELECT * INTO [" + DateTime.Now.ToString().Replace(" ", "_") + "_ImportFromExcel] FROM ImportFromExcel; DROP TABLE ImportFromExcel; END CREATE TABLE ImportFromExcel (InvLakNo INT);";
                        using (SqlCommand sqlCmd = new SqlCommand(sqlQuery, sqlConn))
                        {
                            sqlConn.Open();
                            sqlCmd.ExecuteNonQuery();
                            while (dataReader.Read())
                            {
                                bulkImport.WriteToServer(dataReader);
                            }
                        }
                    }
                }
            }
        }
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        excelApp.Quit();
    }
}

Функция работает без ошибок или предупреждений, и если я заменю WriteToServer с помощью ручных команд SQL строки вставляются; но bulkImport ничего не вставляю

ПРИМЕЧАНИЕ. В этом примере есть только одно поле, и в реальной функции, которую я сейчас запускаю для тестирования; но в конце будут вставлены десятки и десятки полей, и я буду делать ColumnMapping для всех них.

Кроме того, как уже говорилось, я знаю, что мой код, вероятно, ужасен - пожалуйста, не стесняйтесь указывать мне любые указатели, которые вы считаете полезными. Я готов и желаю учиться.

Спасибо!

3 ответа

Решение

Вот пример для чтения информации о схеме из Excel (здесь мы читаем имена таблиц - имена листов с таблицами в них):

private IEnumerable<string> GetTablesFromExcel(string dataSource)
{
    IEnumerable<string> tables;
    using (OleDbConnection con = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;" +
    string.Format("Data Source={0};", dataSource) +
    "Extended Properties=\"Excel 12.0;HDR=Yes\""))
    {
        con.Open();
        var schemaTable = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
        tables = schemaTable.AsEnumerable().Select(t => t.Field<string>("TABLE_NAME")); 
        con.Close();
    }
    return tables;
}

И вот пример, который превращает SBC из Excel в временную таблицу:

void Main()
{
  string sqlConnectionString = @"server=.\SQLExpress;Trusted_Connection=yes;Database=Test";

  string path = @"C:\Users\Cetin\Documents\ExcelFill.xlsx"; // sample excel sheet
  string sheetName = "Sheet1$";

  using (OleDbConnection cn = new OleDbConnection(
    "Provider=Microsoft.ACE.OLEDB.12.0;Data Source="+path+
    ";Extended Properties=\"Excel 8.0;HDR=Yes\""))

  using (SqlConnection scn = new SqlConnection( sqlConnectionString ))
  {

    scn.Open();
    // create temp SQL server table
    new SqlCommand(@"create table #ExcelData 
    (
      [Id] int, 
      [Barkod] varchar(20)
    )", scn).ExecuteNonQuery();

    // get data from Excel and write to server via SBC  
    OleDbCommand cmd = new OleDbCommand(String.Format("select * from [{0}]",sheetName), cn);
    SqlBulkCopy sbc = new SqlBulkCopy(scn);

    // Mapping sample using column ordinals
    sbc.ColumnMappings.Add(0,"[Id]");
    sbc.ColumnMappings.Add(1,"[Barkod]");

    cn.Open();
    OleDbDataReader rdr = cmd.ExecuteReader();
    // SqlBulkCopy properties
    sbc.DestinationTableName = "#ExcelData";
    // write to server via reader
    sbc.WriteToServer(rdr);
    if (!rdr.IsClosed) { rdr.Close(); }
    cn.Close();

    // Excel data is now in SQL server temp table
    // It might be used to do any internal insert/update 
    // i.e.: Select into myTable+DateTime.Now
    new SqlCommand(string.Format(@"select * into [{0}] 
                from [#ExcelData]", 
                "ImportFromExcel_" +DateTime.Now.ToString("yyyyMMddHHmmss")),scn)
        .ExecuteNonQuery();
    scn.Close();
  }
}

Хотя это будет работать, думая в долгосрочной перспективе, вам нужны имена столбцов, и, возможно, их типы различаются, но это может быть излишним, если делать это с помощью SBC, и вы можете вместо этого напрямую сделать это из OpenQuery сервера MS SQL:

SELECT * into ... from OpenQuery(...)  

Я думаю, что было бы очень длинным и грязным ответом, если бы я прокомментировал ваш код, а также дал образцы кода указателя в том же сообщении, поэтому я решил разделить его на два сообщения. Сначала комментарии:

Вы используете автоматизацию, чтобы получить что? У вас уже есть имя листа, как я вижу, и хуже того, что вы делаете app.Quit() в конце. Полностью удалите этот код автоматизации. Если вам нужна информация из Excel (например, имена листов, имена столбцов), вы можете использовать метод GetOleDbSchemaTable OleDbConnecton. Вы можете сделать отображение в основном двумя способами:

  1. Порядковый номер столбца Excel для имени столбца таблицы SQL
  2. Имя столбца Excel в имя столбца таблицы SQL

оба сделают. В общем коде, если у вас одинаковые имена столбцов в обоих источниках, но их порядковый номер и количество могут отличаться, вы можете получить имена столбцов из таблицы схемы OleDbConnection и выполнить отображение в цикле.

Вы удаляете и создаете таблицу с именем "ImportFromExcel" для вставки временных данных, тогда почему бы просто не создать временную таблицу SQL-сервера, используя префикс # в имени таблицы? OTOH этот кусок кода немного странный, он будет выполнять импорт из "ImportFromExcel", если он там есть, затем удалит и создаст новый и попытается выполнить массовый импорт в этот новый. При первом запуске SqlBulkCopy (SBC) будет заполнять ImportFromExcel, а при следующем запуске он будет скопирован в таблицу с именем (DateTime.Now ...), а затем очищен через drop и снова создан. Кстати, наименование:

DateTime.Now.ToString().Replace(" ", "_") + "_ImportFromExcel"

не чувствует себя хорошо. Хотя это выглядит заманчиво, это не сортируется, вероятно, вы хотели бы что-то вроде этого:

DateTime.Now.ToString("yyyyMMddHHmmss") + "_ImportFromExcel"

Или еще лучше:

"ImportFromExcel_" +DateTime.Now.ToString("yyyyMMddHHmmss")

так что у вас есть что-то, что сортируется и выбирается для всех импортов в качестве символа подстановки или зацикливания по какой-то причине.

Затем вы пишете на сервер внутри цикла reader.Read(). Это не тот способ, которым работает WriteToServer. Вы не будете делать Reader.Read (), но просто:

sbc.WriteToServer(reader);

В моем следующем сообщении я приведу простое чтение схемы и простой пример SBC из excel во временную таблицу, а также предложу, как это сделать вместо этого.

WriteToServer(IDataReader) предназначен для внутреннего IDataReader.Read()операция.

using (SqlCommand sqlCmd = new SqlCommand(sqlQuery, sqlConn))
{
    sqlConn.Open();
    sqlCmd.ExecuteNonQuery();
    bulkImport.WriteToServer(dataReader);
}

Вы можете проверить документ MSDN по этой функции, есть рабочий пример: https://msdn.microsoft.com/en-us/library/434atets(v=vs.110).aspx

Другие вопросы по тегам