彻底解决OLEDB连接Excel数据类型不统一的问题

来源:互联网 发布:人类未解之谜 知乎 编辑:程序博客网 时间:2024/05/16 08:24

在使用Microsoft.Jet.OLEDB.4.0连接Excel,进行读取数据,相对使用传统的COM来读取数据,效率是很高的。但相对传统COM操作Excel来说,及存在数据类型转换的问题。

因为使用OLEDB连接Excel读取数据时,需要确定数据的类型。默认情况使用连接字符串:

string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + excelFile + ";Extended Properties='Excel 8.0;'";

 使用上面的连接字符串连接Excel时,可能会遇到数据类型不一致的问题。所谓数据类型不一致,是指同一列里面数据类型可能出现多种,如浮点数、字符串、日期等;当出现此类情况时,读取出来的数据就为空,甚至会报错,如“非法的日期格式”等异常。出现这种问题,我们大家都会想到把数据全部都按字符数据来读取,但是按什么数据类型来读取不是我们能控制的,是OLEDB控制的,至少暂时我还没有找到能控制输出数据类型的方法。因为我当初也尝试使用convert,cast函数对输出的列进行类型转换,但oledb连接Excel时,使用的SQL不支持这些函数。因此只能从其他角度来解决该问题。我也在网上搜索了很多解决方法,最全面的解决方法是:http://www.douban.com/note/18510346/。下面列出了网上出现解决该问题方法的比较:

解决方案说明缺点COM使用Excel COM接口访问Excel非托管、不容易释放资源、效率低下连接字符串添加IMEX=1构造的连接字符串,如:

string strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + excelFile + ";Extended Properties='Excel 8.0;HDR=YES;IMEX=1'";
其中HDR表示是否将Sheet页的第一行作为字段名,“YES”代表是,“NO”代表不是,当为YES时,将把SHEET页的第一行作为字段名,数据从第二行开始,而如果是NO时,字段名就是要SHEET的列名,如A,B,C等,数据就从第一行开始取;IMEX是用来告诉驱动程序,使用Excel文件的模式,其值有0、1、2三种,分别代表导出、导入、混合模式。当我们设置IMEX=1时将强制混合数据转换为文本,但仅仅这种设置并不可靠,IMEX=1只确保在某列前8行数据至少有一个是文本项的时候才起作用,它只是把查找前8行数据中数据类型占优选择的行为作了略微的改变。例如某列前8行数据全为纯数字,那么它仍然以数字类型作为该列的数据类型,随后行里的含有文本的数据仍然变空。 (摘至:http://www.douban.com/note/18510346/)。只根据前8行数据判断是否使用字符类型IMEX=1与注册表值TypeGuessRows配合使用TypeGuessRows 值决定了ISAM 驱动程序从前几条数据采样确定数据类型,默认为“8”。可以通过修改“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Jet/4.0/Engines/Excel”下的该注册表值来更改采样行数。但是这种改进还是没有根本上解决问题,即使我们把IMEX设为“1”, TypeGuessRows设得再大,例如1000,假设数据表有1001行,某列前1000行全为纯数字,该列的第1001行又是一个文本,ISAM驱动的这种机制还是让这列的数据变成空。 (摘至:http://www.douban.com/note/18510346/)。修改注册表不方便,而且无法事先判读sheet有多少行,因此还是受行数限制。将Excel先转换成csv纯文本格式

(1)在读取Excel的.xls类型的文本数据之前,先将其转换为.csv格式,在Excel中直接另存为这种格式就可以达到转换的目的。CSV文件又称为逗号分隔的文件,是一种纯文本文件,它以“,”分隔数据列。

  

需要指出的是,CSV文件也可以用Ole DB或ODBC的方式读取,但是如果采用这些方式读取其数据又会回到丢失数据的老路上,ISAM机制同样会发挥作用。

  

(2)采用普通的读取文本文件的方法打开文件,读取第一行,用“,”作为分隔符获得各字段名,在DataTable中创建对应的各字段,字段的类型可以统一创建成“String”。

(3)逐行读取数据行, 用“,”作为分隔符获得某行各列的数据并填入DataTable相应的字段中。

简要代码:

String line; String [] split = null; DataTable table=new DataTable("auto"); DataRow row=null; StreamReader sr=new StreamReader("c:/auto.csv",System.Text.Encoding.Default); //创建与数据源对应的数据列 line = sr.ReadLine(); split=line.Split(','); foreach(String colname in split){   table.Columns.Add(colname,System.Type.GetType("System.String")); } //将数据填入数据表 int j=0; while((line=sr.ReadLine())!=null){   j=0;   row = table.NewRow();   split=line.Split(',');   foreach(String colname in split){       row[j]=colname;       j++;   }   table.Rows.Add(row);} sr.Close(); //显示数据 dataGrid1.DataSource=table.DefaultView; dataGrid1.DataBind(); 
(摘至:http://www.douban.com/note/18510346/)。需要事先将excel转换成csv文件

这里提供一个更加方便的办法,不过前提是第一行必须是作为字段名或者第一行的数据类型就为字符型。这样一说,大家就明白了。首先修改连接字符串为:

string strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + excelFile + ";Extended Properties='Excel 8.0;HDR=NO;IMEX=1'";

这里将HDR设为NO,因为我就是将第一行做为数据读取,而IMEX=1就表示根据前8行判断列的数据类型,如果有字符型数据,那么就强制混合数据转换为文本。这里就明白为什么要保证第一行为字符型的原因了。能将列的数据类型强制设为字符型,那么列中出现什么类型的数据都不怕了。需要做的工作就是,在获取完数据后,将字段名重新设置,并删除第一条记录即可。代码如下:

DataTable dt = new DataTable();using(OleDbCommand cmd = new OleDbCommand()){    cmd.Connection = conn;    cmd.CommandType = CommandType.Text;    cmd.CommandTimeout = 6;    cmd.CommandText = string.Format("select * from [{0}$]", sheetName);    OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);    adapter.Fill(dt);}if (dt.Rows.Count > 0) {    DataRow dr = dt.Rows[0];    for (int col = 0; col < dt.Columns.Count; col++) {        dt.Columns[col].ColumnName = dr[col].ToString();    }    dt.Rows[0].Delete();    dt.AcceptChanges();}

这样最终得到的数据就是你希望的数据了。