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

Posted

技术标签:

【中文标题】C#中的SQL批量插入不插入值【英文标题】:SQL Bulk Insert in C# not inserting values 【发布时间】:2017-04-04 08:36:37 【问题描述】:

我对 C# 完全陌生,所以我确信我会得到很多关于我的代码是如何格式化的 cmets - 我欢迎他们。请随时提出您在此过程中可能提出的任何建议或建设性批评。

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

出于这个原因,我决定使用 SQL Bulk Insert 方法,因为我不知道用户在任何给定的执行过程中是否只会插入 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();
    

函数运行时没有错误或警告,如果我用手动 SQL 命令替换 WriteToServer,则插入行;但bulkImport 没有插入任何东西。

注意:本例中只有一个字段,而在我当前运行测试的实际函数中;但最终会有几十个字段被插入,我将为所有字段做一个ColumnMapping

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

谢谢!

【问题讨论】:

与您的问题无关,但您应该关闭与OleDbConnectionSqlConnection 的连接。 谢谢。我会注意添加的! 【参考方案1】:

如果我对您的代码发表评论并在同一条消息中提供指针示例代码,我认为这将是一个非常冗长且混乱的答案,因此我决定将其分为两条消息。评论第一:

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

    Excel 列序号到 SQL 表列名 Excel 列名到 SQL 表列名

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

您正在删除并创建一个名为“ImportFromExcel”的表以插入 temp 数据,那么为什么不简单地使用 # 创建一个 temp 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 到 temp 表中,以及您应该如何做的建议。

【讨论】:

非常感谢您的帮助,但我并不完全理解您的评论“完全删除该自动化代码”。我想问题是我对这些术语不够熟悉——我在“自动化”什么?为什么我在最后做App.Quit() 会“更糟”?当我打开应用程序的新实例时,它实际上会打开一个后台 Excel,我需要在完成导入操作后关闭它。如果这不正确,请随时告诉我应该如何解决该问题。我真的很感激! 只是为了让您了解将数据移动到过期表中的 SQL 命令,原因只是为了测试目的。该部分不会进入最终产品。 这个:excelApp = new Excel.Application(); excelBook = excelApp.Workbooks.Open(excelFilePath); excelSheet = excelBook.Worksheets.get_Item(sheetName); excelRange = excelSheet.UsedRange; excelBook.Close(0);是自动化代码,没有任何目的。还是我错过了? 我希望我的无知不会破坏你的耐心,但我真的不知道什么是“自动化代码”。话虽如此,这些变量的原因仅仅是用户需要为每次导入选择一个具有一个特定工作表名称的特定文件,而这些文件/名称并不总是相同的。正如你所说,我可以使用 OLE 来解决这个问题,所以也许我根本不需要所有这些? 不,你不会破坏我的耐心 :) 我知道事情有时会很艰难。希望我知道如何将这些 cmets 移动到聊天中,当 cmets 达到大量时,它可能会自动执行此操作。顶部使用 Excel.Application 的代码是 Excel 自动化代码,它使用 Excel VBA。在这里它没有任何目的。用户可以通过 OpenFileDialog 选择 excel 文件,一旦获得 xlsx 文件名,就可以使用 OleDbConnection 连接到它,并使用 GetOleDbSchemaTable() 方法获取各种信息。 IOW 我的意思是,您的代码不需要自动化。【参考方案2】:

这是从 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 转换为 temp 表的示例:

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(...)  

【讨论】:

【参考方案3】:

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

【讨论】:

以上是关于C#中的SQL批量插入不插入值的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL Server(C# 客户端)中批量插入大量数据的最快方法是啥

C# 中的批量更新

SQL表值参数批量插入

oracle——数据表的相关操作——插入数据以及批量插入数据

读取逗号分隔值并批量插入SQL表

批量SQL插入故障排除