简体   繁体   English

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

[英]SQL Bulk Insert in C# not inserting values

I'm completely new to C#, so I'm sure I'm going to get a lot of comments about how my code is formatted - I welcome them. 我是C#的新手,所以我确定我会收到很多有关我的代码格式的评论-我欢迎他们。 Please feel free to throw any advice or constructive criticisms you might have along the way. 请随时提出您的建议或建设性批评。

I'm building a very simple Windows Form App that is eventually supposed to take data from an Excel file of varying size, potentially several times per day, and insert it into a table in SQL Server 2005. Thereafter, a stored procedure within the database takes over to perform various update and insert tasks depending on the values inserted into this table. 我正在构建一个非常简单的Windows Form应用程序,该应用程序最终应从大小可能不同的Excel文件中获取数据(每天可能有几次),并将其插入SQL Server 2005中的表中。此后,在数据库中存储了一个过程接管执行各种更新和插入任务,具体取决于插入此表中的值。

For this reason, I've decided to use the SQL Bulk Insert method, since I can't know if the user will only insert 10 rows - or 10,000 - at any given execution. 由于这个原因,我决定使用SQL批量插入方法,因为我不知道用户在任何给定的执行情况下是否只会插入10行或10,000行。

The function I'm using looks like this: 我正在使用的功能如下所示:

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();
    }
}

The function runs without errors or warnings, and if I replace the WriteToServer with manual SQL commands, the rows are inserted; 该函数运行时没有错误或警告,如果我用手动SQL命令替换了WriteToServer ,则会插入行; but the bulkImport isn't inserting anything. 但是bulkImport没有插入任何内容。

NOTE: There is only one field in this example, and in the actual function I'm currently running to test; 注意:在此示例中,只有一个字段,而在我当前正在运行的实际功能中进行测试; but in the end there will be dozens and dozens of fields being inserted, and I'll be doing a ColumnMapping for all of them. 但是最后将插入数十个字段,我将为所有这些字段做一个ColumnMapping

Also, as stated, I am aware that my code is probably horrible - please feel free to give me any pointers you deem helpful. 另外,如上所述,我知道我的代码可能很可怕-请随时给我您认为有帮助的任何指针。 I'm ready and willing to learn. 我准备好并且愿意学习。

Thanks! 谢谢!

I think it would be a very long and messy answer if I commented on your code and also gave pointer sample codes in the same message, so I decided to divide then into two messages. 如果我评论您的代码并在同一条消息中提供指针示例代码,我认为这将是一个非常冗长且混乱的答案,因此我决定将其分为两部分。 Comments first: 首先评论:

You are using automation to get what? 您正在使用自动化来获得什么? You already have the sheet name as I see it and worse you are doing app.Quit() at the end. 正如我所看到的,您已经具有工作表名称,更糟糕的是,您在最后执行app.Quit()。 Completely remove that automation code. 完全删除该自动化代码。 If you needed some information from excel (like sheet names, column names) then you could use OleDbConnecton's GetOleDbSchemaTable method. 如果您需要来自excel的一些信息(例如工作表名称,列名称),则可以使用OleDbConnecton的GetOleDbSchemaTable方法。 You might do the mapping basically in 2 ways: 您基本上可以通过两种方式进行映射:

  1. Excel column ordinal to SQL table column name Excel列与SQL表列名称的序号
  2. Excel column name to SQL table column name Excel列名称到SQL表列名称

both would do. 两者都会做。 In a generic code, assuming you have column names same in both sources, but their ordinal and count may differ, you could get the column names from OleDbConnection schema table and do the mapping in a loop. 在通用代码中,假设两个源中的列名相同 ,但是序号和计数可能不同,则可以从OleDbConnection架构表中获取列名并在循环中进行映射。

You are dropping and creating a table named "ImportFromExcel" for the purpose of temp data insertion, then why not simply create a temp SQL server table by using a # prefix in table name? 您要删除并创建一个名为“ ImportFromExcel”的表以用于临时数据插入,那么为什么不通过在表名中使用#前缀简单地创建一个临时 SQL Server表呢? OTOH that code piece is a little weird, it would do an import from "ImportFromExcel" if it is there, then drop and create a new one and attempt to do bulk import into that new one. OTOH那段代码有点奇怪,如果存在,它将从“ ImportFromExcel”进行导入,然后拖放并创建一个新的,并尝试将批量导入到该新的。 In first run, SqlBulkCopy (SBC) would fill ImportFromExcel and on next run it would be copied to a table named (DateTime.Now ...) and then emptied via drop and create again. 在第一次运行中,SqlBulkCopy(SBC)将填充ImportFromExcel,在下次运行时,它将被复制到名为(DateTime.Now ...)的表中,然后通过放置清空并再次创建。 BTW, naming: 顺便说一句,命名:

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

doesn't feel right. 感觉不对。 While it looks tempting, it is not sortable, probably you would want something like this instead: 虽然看起来很诱人,但它不是可排序的,可能您想要这样的东西:

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

Or better yet: 或者更好:

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

so you would have something that is sorted and selectable for all the imports as a wildcard or looping for some reason. 因此,由于某种原因,对于所有导入,您都可以将它们排序并选择为通配符或循环。

Then you are writing to server inside a reader.Read() loop. 然后,您将在reader.Read()循环内写入服务器。 That is not the way WriteToServer works. 这不是WriteToServer的工作方式。 You wouldn't do reader.Read() but simply: 您不会做reader.Read(),而只是:

sbc.WriteToServer(reader);

In my next message e I will give simple schema reading and a simple SBC sample from excel into a temp table, as well as a suggestion how you should do that instead. 在我的下一条消息e中,我将简单地读取模式并将简单的SBC示例从excel导入到临时表中,并建议您应该如何做。

the WriteToServer(IDataReader) is intended to do internally the IDataReader.Read() operation. WriteToServer(IDataReader)用于内部执行IDataReader.Read()操作。

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

You can check the MSDN doc on that function, has a working example: https://msdn.microsoft.com/en-us/library/434atets(v=vs.110).aspx 您可以检查该函数的MSDN文档,并提供有效的示例: https : //msdn.microsoft.com/zh-cn/library/434atets(v=vs.110).aspx

Here is the sample for reading schema information from Excel (here we read the tablenames - sheet names with tables in them): 这是从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;
}

And here is a sample that does SBC from excel into a temp table: 这是一个将excel中的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();
  }
}

While this would work, thinking in the long run, you need column names, and maybe their types differ, it might be an overkill to do this stuff using SBC and you might instead directly do it from MS SQL server's OpenQuery: 从长远来看,这可能行得通,但您需要列名,并且它们的类型可能不同,但是使用SBC进行此操作可能是一个过大的选择,而您可以直接从MS SQL Server的OpenQuery进行操作:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM