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 方法。
您基本上可以通过两种方式进行映射:
- Excel 列序号 SQL table 列名
- 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(...)
我是 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 方法。 您基本上可以通过两种方式进行映射:
- Excel 列序号 SQL table 列名
- 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(...)