说到Excel,那是叫个怕啊!一吧自己对Excel不熟悉,二吧大家都说Excel不好操作;心里上不知不觉就对它产生了畏惧。但是有句话说的好啊:你最怕的地方就是你最弱的地方。想要进步还是得想办法提高自己,就去搞自己最怕的!
其实说到怕,也并不是真的怕,而是对这方面不熟悉造成的;想要解决怕的问题,就得把它整明白了,下次在遇见就不怕不怕了;最近帮同学做了个Excel处理的小程序;涉及到了一下Excel的操作,特此给大家分享一下。
1. Excel导入到数据库的操作
(1)、通过NPOI来实现。NPOI是个好东东,它能方便的让你对Excel的控制达到单元格级别;而且使用起来也比较方便,不需要依赖office的什么组件;基本上引用了官方提供的那几个dll就可以使用了。我在使用的过程中遇到了一个问题,NPOI提示缺少一个.dll,叫做:"ICSharpCode.SharpZipLib.dll",后来查了查资料,了解到Excel2007或2010的.xlsx格式的文件其实是压缩文件,NPOI在处理.xlsx文件的时候需要先解压缩,而解压缩就用到了ICSharpCode.SharpZipLib.dll,这是个比较常用的dll,可能系统由于某种原因造成了dll的丢失,然后从网上找了好久才找到合适的dll文件,有需要的童鞋可以点击"ICSharpCode.SharpZipLib.dll"下载我找的dll试试。
NPOI的具体操作我就不再啰嗦了,大家可以点击"学习NPOI"去仔细研究;通过NPOI操作Excel是个不错的选择,在数据量不是特别大的时候可以考虑使用;因为NPOI在加载Excel的时候需要读取每个单元格的样式、格式等信息,所以不太适合大数据量的操作。
(2)、使用SqlBulkCopy类。类描述:使您可以用其他源的数据有效批量加载 SQL Server 表。吊,微软的提供的类,想必很好用。通过程序测试了下,25W行的数量需要5秒左右;用法我大致说一下,不清楚的可以MSDN查查。
思路:把Excel中的数据提取到内存DataTable中,然后通过SqlBulkCopy类写入到数据库,贴代码:

public static void InsertDBFromExcelData(string excelFile, string tableName, SqlBulkCopyColumnMapping[] mappings) { string mystring = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = '{0}';Extended Properties='Excel 12.0; HDR=Yes; IMEX=1'".With(excelFile); OleDbConnection cnnxls = new OleDbConnection(mystring); var allSheetNames = GetSheetNames(excelFile); foreach (string sheetName in allSheetNames) { OleDbDataAdapter myDa = new OleDbDataAdapter("select * from [{0}]".With(sheetName), cnnxls); DataTable dt = new DataTable(); myDa.Fill(dt); if (dt.Rows.Count > 0) { using (SqlBulkCopy copy = new SqlBulkCopy(DBConnectionStr)) //与目标服务器连接 { copy.BulkCopyTimeout = 5000; copy.DestinationTableName = tableName; //导入到数据库的表名 if (mappings != null) { foreach (var item in mappings) { copy.ColumnMappings.Add(item); } } copy.WriteToServer(dt); } } } }
2.从数据库导出到Excel
(1)、使用NPOI写入,具体用法我就不说了;测试的时候,循环写入25W个单元格大致需要4~8秒,已经相当快了(未设置单元格的样式等信息)。
(2)、使用利用驱动来实现。比如Sqlserver数据库,可以这么写sql语句: SELECT * FROM INTO [SheetName] TABLE in "" [ODBC;Driver=SQL SERVER;Data Source=.;DataBase=sa;Integrated security=true] ;然后这样写执行语句:
string cnnString = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = 'D:\Files\test.xlsx';Extended Properties='Excel 12.0; HDR=NO; IMEX=0'";
using (OleDbConnection cnnxls = new OleDbConnection(cnnString)) { cnnxls.Open(); OleDbCommand cmd = new OleDbCommand(sql, cnnxls); cmd.ExecuteNonQuery(); cnnxls.Close(); }
看sql语句和执行的链接串的写法,大致也能猜出个什么意思。 Provider = Microsoft.Ace.OleDb.12.0 表示驱动程序是OleDb.12.0,Extended Properties='Excel 12.0; HDR=yes; IMEX=0' 表示的链接的扩展属性:Excel12.0表示的是链接的Excel版本,如果是电脑中只安装了Excel2003,则需修改为:Excel4.0;如果安装了Excel2007或2010则不用修改了,一样能处理.xls和.xlsx文件;"HDR=yes;"是说第一行是列名而不是数据,"HDR=no;"正好与前面的相反,把第一行也当成数据处理,这样导入到数据库表中的数据就会多出第一行的标题数据(如果你的Excel文件的第一行是标题);另外说明一下IMEX的意义,不写了,抄了点儿,挺详细的:
当 IMEX=0 时为“汇出模式”,这个模式开启的 Excel 档案只能用来做“写入”用途。
当 IMEX=1 时为“汇入模式”,这个模式开启的 Excel 档案只能用来做“读取”用途。
当 IMEX=2 时为“连結模式”,这个模式开启的 Excel 档案可同时支援“读取”与“写入”用途。
意义如下:
0 ---输出模式;
1---输入模式;
2----链接模式(完全更新能力)
使用该方法可以快速的把Excel中的数据导入到数据库中,我测试的是10列,25W行数据导入到Excel的时间只需要3秒即可;但是这个方法也有缺点,他不能对Excel的数据进行再加工;例如Excel数据格式的转变,或者计算列等需要深加工的数据就没什么办法了;比如Excel中有一列是数字,但是当导入数据库时可能就变成了科学表示法的文本了(10+E2),这样的问题要么改Excel的单元格格式,要么在取的时候转换一下。
另外遇到了一个奇怪的问题,就是去Excel所有工作簿的名字的问题,有时候明明一个工作簿,但是通过普通的方法总是能取到2个名字,比如:有一个工作簿:mysheet1;但是查询到会存在两个:mysheet1,mysheet1$,但其实不存在第二个工作簿。没有深究,写了方法干掉了第二个:
public static string[] GetSheetNames(string file) { List<string> names = new List<string>(); string mystring = "Provider = Microsoft.Ace.OleDb.12.0 ; Data Source = '{0}';Extended Properties='Excel 12.0;'".With(file); using (OleDbConnection oleConn = new OleDbConnection(mystring)) { oleConn.Open(); DataTable dtOle = oleConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" }); DataTableReader dtReader = new DataTableReader(dtOle); List<SheetName> allSheetNames = new List<SheetName>(); foreach (DataRow row in dtOle.Rows) { allSheetNames.Add(new SheetName(row)); } allSheetNames = allSheetNames.Distinct(new SheetNameEquatilyCompare()).ToList(); allSheetNames.ForEach(f => { names.Add(f.Name); }); dtOle = null; oleConn.Close(); } return names.Distinct().ToArray(); } //工作簿名称自定义比较器 class SheetNameEquatilyCompare : IEqualityComparer<SheetName> { public bool Equals(SheetName x, SheetName y) { return x.ModifyTime == y.ModifyTime && x.CreateTime == y.CreateTime && x.Name.TrimEnd('$') == y.Name.TrimEnd('$'); } public int GetHashCode(SheetName obj) { return 10; } }