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

时间:2017-04-04 08:36:38

标签: c# sqlbulkcopy oledbdatareader

我对C#完全陌生,所以我确定我会对我的代码格式化得到很多评论 - 我欢迎他们。请随时提出您可能提出的任何建议或建设性批评。

我构建了一个非常简单的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

此外,如上所述,我知道我的代码可能很糟糕 - 请随时给我任何你认为有帮助的指针。我准备好并愿意学习。

谢谢!

3 个答案:

答案 0 :(得分:1)

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

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

  1. Excel列序号到SQL表列名称
  2. Excel列名称为SQL表列名称
  3. 两个人都会这样做。在通用代码中,假设两个源中的列名称相同,但它们的序数和计数可能不同,您可以从OleDbConnection架构表中获取列名,并在循环中执行映射。

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

    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 表,以及建议你应该如何做到这一点。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

以下是从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;
}

这是一个从excel到 temp 表执行SBC的示例:

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服务器上做到这一点&#39;的OpenQuery:

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