我对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
。
此外,如上所述,我知道我的代码可能很糟糕 - 请随时给我任何你认为有帮助的指针。我准备好并愿意学习。
谢谢!
答案 0 :(得分:1)
如果我评论你的代码并在同一条消息中提供指针示例代码,我认为这将是一个非常冗长而混乱的答案,因此我决定将其分成两条消息。评论第一:
您正在使用自动化获取什么?你已经有了我看到的工作表名称,更糟糕的是你最后正在做app.Quit()。完全删除该自动化代码。 如果您需要来自excel的一些信息(如工作表名称,列名称),那么您可以使用OleDbConnecton的GetOleDbSchemaTable方法。 您可以基本上以两种方式进行映射:
您正在删除并创建一个名为" 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(...)