SQL 在 C# 中批量插入不插入值

SQL Bulk Insert in C# not inserting values

我是 C# 的新手,所以我确信我会收到很多关于我的代码格式的评论 - 我欢迎他们。请随时提出您在此过程中可能有的任何建议或建设性批评。

我正在构建一个非常简单的 Windows 表单应用程序,它最终应该从不同大小的 Excel 文件中获取数据,每天可能多次,并将其插入 table 在 SQL Server 2005 中。此后,数据库中的存储过程接管执行各种更新和插入任务,具体取决于插入到此 table.

中的值

出于这个原因,我决定使用 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

此外,如前所述,我知道我的代码可能很糟糕 - 请随时给我任何您认为有帮助的建议。我已经准备好并愿意学习。

谢谢!

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

我认为如果我评论你的代码并在同一条消息中给出指针示例代码,那将是一个非常冗长和混乱的答案,所以我决定将其分成两条消息。先评论:

您正在使用自动化来获得什么?正如我所见,您已经有了 sheet 名称,更糟糕的是您最后还在做 app.Quit() 。完全删除该自动化代码。 如果您需要来自 excel 的一些信息(例如 sheet 名称、列名称),那么您可以使用 OleDbConnecton 的 GetOleDbSchemaTable 方法。 您基本上可以通过两种方式进行映射:

  1. Excel 列序号 SQL table 列名
  2. Excel列名到SQLtable列名

两者都可以。在通用代码中,假设您在两个源中都有列名 相同 ,但它们的序号和计数可能不同,您可以从 OleDbConnection 模式 table 中获取列名并执行循环映射。

您正在删除并创建一个名为 "ImportFromExcel" 的 table 用于 temp 数据插入,那么为什么不简单地创建一个 temp SQL 服务器 table 通过在 table 名称中使用 # 前缀? OTOH 那个代码片段有点奇怪,它会从 "ImportFromExcel" 导入,如果它在那里,然后删除并创建一个新的,并尝试批量导入到那个新的。在第一个 运行 中,SqlBulkCopy (SBC) 将填充 ImportFromExcel 并在下一个 运行 中将其复制到名为 (DateTime.Now ...) 的 table然后通过 drop 清空并再次创建。顺便说一句,命名:

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

感觉不对。虽然它看起来很诱人,但它并不好table,可能你会想要这样的东西:

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

或者更好:

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

所以你会有一些东西被排序并选择table作为通配符或出于某种原因循环的所有导入。

然后您将在 reader.Read() 循环内写入服务器。这不是 WriteToServer 的工作方式。你不会做 reader.Read() 而只是:

sbc.WriteToServer(reader);

在我的下一条消息 e 中,我将提供简单的模式读取和从 excel 到 temp table 的简单 SBC 示例,以及建议你应该怎么做。

这是从 Excel 中读取模式信息的示例(这里我们读取 table 名称 - sheet 名称中包含 tables):

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 转换为 temp table:

的示例
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 执行此操作可能有点矫枉过正,您可以直接从MS SQL 服务器的 OpenQuery:

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