OleDB 和混合 Excel 数据类型:缺少数据

Posted

技术标签:

【中文标题】OleDB 和混合 Excel 数据类型:缺少数据【英文标题】:OleDB & mixed Excel datatypes : missing data 【发布时间】:2011-03-15 00:30:41 【问题描述】:

我有一个要读入数据表的 Excel 工作表 - 除了我的 Excel 工作表中的一个特定列之外,一切都很好。 “ProductID”列是##########n######### 等值的混合。

我尝试将let OleDB handle everything by itself automatically 读入数据集/数据表,但“ProductID”中的任何值(如n######)都丢失、忽略并留空。我尝试通过使用数据读取器遍历每一行来手动创建我的 DataTable,但结果完全相同。

代码如下:

// add the column names manually to the datatable as column_1, column_2, ...
for (colnum = 0; colnum < num_columns; colnum ++)
  ds.Tables["products"].Columns.Add("column_" +colnum , System.Type.GetType("System.String")); 

while(myDataReader.Read())
  // loop through each excel row adding a new respective datarow to my datatable 
  DataRow a_row = ds.Tables["products"].NewRow();
  for (col = 0; col < num_columns; col ++)
    try   a_row[col] = rdr.GetString(col);  
    catch   a_row[col] = rdr.GetValue(col).ToString(); 
  
  ds.Tables["products"].Rows.Add(a_row);

我不明白为什么它不允许我读取像 n###### 这样的值。我怎样才能做到这一点?

【问题讨论】:

【参考方案1】:

快捷方式 --> 如果您在 Excel 中有混合类型的列:将列 Z 排序为 A

我几乎浏览了这里的所有答案,其中一些对我有用,有些没有,但是对我来说没有一个是可取的,因为不知何故 ADO 没有在我的 Excel 中的混合类型列中选择数据文件。我必须设置HDR=NO 以使ADO 读取我的电子表格列,该列是文本和数字的混合,这样我就失去了在我的SQL 语句中使用列标题的能力,这是不好的。如果 Excel 文件中的列顺序发生变化,SQL 语句将导致错误或错误输出。

在混合数据类型列中,键是前 8 行。 ADO 根据前 8 行确定列的数据类型 因此,如果您仍想使用扩展参数修改连接字符串,只需在 ADO 读取数据之前将 Excel 文件中的 Z 列排序为 A所以这样顶部的行是文本行,然后您的列将被选为文本。

如果您的初始行是数字(无论您的列是否设置为 Excel 中的 TEXT 格式)ADO 会将这些列确定为数字类型,因此一旦读取下面的文本行,它就无法将它们转换为数字。在相反的情况下,如果列是确定的文本,如果任何行是数字,则可以将其转换为文本。

【讨论】:

【参考方案2】:

我发现有几个论坛声称通过将IMEX=1;TypeGuessRows=0;ImportMixedTypes=Text 添加到连接字符串中的扩展属性可以解决问题,但事实并非如此。我终于通过在连接字符串中的扩展属性中添加“HDR=NO”解决了这个问题(如上面的 Brian Wells 所示),以便我可以导入混合类型。

然后我添加了一些通用代码来命名第一行数据之后的列,然后删除第一行。

    public static DataTable ImportMyDataTableFromExcel(string filePath)
    
        DataTable dt = new DataTable();

        string fullPath = Path.GetFullPath(filePath);

        string connString =
           "Provider=Microsoft.Jet.OLEDB.4.0;" +
           "Data Source=\"" + fullPath + "\";" +
           "Extended Properties=\"Excel 8.0;HDR=No;IMEX=1;\"";

        string sql = @"SELECT * FROM [sheet1$]";

        using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter(sql, connString))
        
            dataAdapter.Fill(dt);
        

        dt = BuildHeadersFromFirstRowThenRemoveFirstRow(dt);

        return dt;
    

    private static DataTable BuildHeadersFromFirstRowThenRemoveFirstRow(DataTable dt)
    
        DataRow firstRow = dt.Rows[0];

        for (int i = 0; i < dt.Columns.Count; i++)
        
            if(!string.IsNullOrWhiteSpace(firstRow[i].ToString())) // handle empty cell
              dt.Columns[i].ColumnName = firstRow[i].ToString().Trim();
        

        dt.Rows.RemoveAt(0);

        return dt;
    

【讨论】:

在 Excel 电子表格中访问数据的所有这些困难引发了一个明显的问题:为什么 Microsoft 不提供比使用蹩脚的 Jet 驱动程序更现代(和高性能)的机制来获取数据?有许多 3rd 方工具可以比 MS 提供的任何工具做得更好。 @3Sphere 你知道可以读取 XLS 文件的东西吗?我尝试了一些东西,但它们只能读取较新的 XLSX grrr @Simon_Weaver 该线程中讨论的机制虽然笨拙,但提供了一种从 XLS 文件中提取数据的可靠(但性能极差)的方法。如果您需要更快的东西,您要么必须编写自己的解析器,要么购买第 3 方组件(例如 Spreadsheet Gear) 这种技术效果很好,并且是比其他答案更通用的解决方案 - 除非文件在第 1 行的 2 个单元格中有重复数据,它会引发异常。我提出了修订,但被拒绝了,所以如果复制这个解决方案要小心!【参考方案3】:

使用 .Net 4.0 并读取 Excel 文件时,我遇到了与 OleDbDataAdapter 类似的问题 - 即在 MS Excel 中的“PartID”列上读取混合数据类型,其中 PartID 值可以是数字(例如 561)或文本(例如 HL4354),即使 excel 列被格式化为“文本”。

据我所知,ADO.NET 根据列中的大多数值选择数据类型(与数字数据类型相关)。即,如果样本集中的大部分 PartID 都是数字的,ADO.NET 将声明该列是数字的。因此,ADO.Net 将尝试将每个单元格转换为一个数字,这对于“文本”PartID 值将失败,并且不会导入那些“文本”PartID。

我的解决方案是将OleDbConnection 连接字符串设置为使用Extended Properties=IMEX=1;HDR=NO 来指示这是一个导入并且表将不包含标题。 excel 文件有一个标题行,所以在这种情况下告诉 ado.net 不要使用它。然后稍后在代码中,从数据集中删除该标题行,然后瞧瞧您为该列混合了数据类型。

string sql = "SELECT F1, F2, F3, F4, F5 FROM [sheet1$] WHERE F1 IS NOT NULL";

OleDbConnection connection = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + PrmPathExcelFile + @";Extended Properties=""Excel 8.0;IMEX=1;HDR=NO;TypeGuessRows=0;ImportMixedTypes=Text""");

OleDbCommand cmd = new OleDbCommand(sql, connection);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);

DataSet ds = new DataSet();
ds.Tables.Add("xlsImport", "Excel");
da.Fill(ds, "xlsImport");

// Remove the first row (header row)
DataRow rowDel = ds.Tables["xlsImport"].Rows[0];
ds.Tables["xlsImport"].Rows.Remove(rowDel);

ds.Tables["xlsImport"].Columns[0].ColumnName = "LocationID";
ds.Tables["xlsImport"].Columns[1].ColumnName = "PartID";
ds.Tables["xlsImport"].Columns[2].ColumnName = "Qty";
ds.Tables["xlsImport"].Columns[3].ColumnName = "UserNotes";
ds.Tables["xlsImport"].Columns[4].ColumnName = "UserID";

connection.Close(); 

// 现在您可以使用 LINQ 搜索字段

    var data = ds.Tables["xlsImport"].AsEnumerable();
    var query = data.Where(x => x.Field<string>("LocationID") == "COOKCOUNTY").Select(x =>
                new Contact
                
                    LocationID= x.Field<string>("LocationID"),
                    PartID = x.Field<string>("PartID"),
                    Quantity = x.Field<string>("Qty"),
                    Notes = x.Field<string>("UserNotes"),
                    UserID = x.Field<string>("UserID")
                );

【讨论】:

man "Excel 8.0;IMEX=1;HDR=NO;TypeGuessRows=0;ImportMixedTypes=Text" 为某人节省了一天 It appears that the ImportMixedTypes can't be set in the connection string, with the ACE OleDb driver, which you need for Excel 12.0 @TheVillageIdiot - 谢谢伙计,为我节省了很多时间。 其实是“扩展属性=IMEX=1;”解决问题。无需包含 HDR=NO。不过还是谢谢 为避免可安装的 ISAM 错误,您需要在扩展属性内容周围添加引号。扩展属性="Excel8.0;IMEX=1"【参考方案4】:

sh4 没问题,很高兴它对混合类型问题有所帮助。

DateTime 列是我记得在过去让我感到悲伤的另一种动物......我们有一个我们处理的 Excel 文件,OleDbDataAdapter 有时会将日期转换为双精度数据类型(显然 Excel 将日期存储为双精度,这编码自 1900 年 1 月 0 日以来经过的天数)。

解决方法是使用:

OleDbConnection mobjExcelConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + txtExcelFile.Text + @";Extended Properties=""Excel 8.0;IMEX=1;HDR=Yes;""");

OleDbDataAdapter mobjExcelDataAdapter = new OleDbDataAdapter("Select * from [" + txtSheet.Text + "$] where [Supplier ID] <> '' ", mobjExcelConn);


DateTime dtShipStatus = DateTime.MinValue;
shipStatusOrig = excelRow["Est Ship Date"].ToString(); // excelRow is DataRow in the DataSet via the OleDbDataAdapter             

if (shipStatusOrig != string.Empty)

    // Date may be read in via oledb adapter as a double
    if (IsNumeric(shipStatusOrig))
    
        double d = Convert.ToDouble(shipStatusOrig);
        dtShipStatus = DateTime.FromOADate(d);

        if (DateTime.TryParse(dtShipStatus.ToString(), out dtShipStatus))
        
            validDate = true;
            Debug.WriteLine("0 converted: ", dtShipStatus.ToString("s"));
        
    
    else
    
        if (ValidateShipDate(shipStatusOrig))
        
            dtShipStatus = DateTime.Parse(shipStatusOrig);
            validDate = true;
            Debug.WriteLine("0 converted: ", dtShipStatus.ToString("s"));
        
        else
        
            validDate = false;
            MessageBox.Show("Invalid date format in the Excel spreadsheet.\nLine # " + progressBar1.Value + ", the 'Ship Status' value '" + shipStatusOrig + "' is invalid.\nDate should be in a valid date time format.\ne.g. M/DD/YY, M.D.Y, YYYY-MM-DD, etc.", "Invaid Ship Status Date");
        
    
...

        public static Boolean IsNumeric (Object Expression)
        
            if(Expression == null || Expression is DateTime)
                return false;

            if(Expression is Int16 || Expression is Int32 || Expression is Int64 || Expression is Decimal || Expression is Single || Expression is Double || Expression is Boolean)
                return true;

            try
            
                if(Expression is string)
                    Double.Parse(Expression as string);
                else
                   Double.Parse(Expression.ToString());
                return true;
             catch  // just dismiss errors but return false

            return false;
        

        public bool ValidateShipDate(string shipStatus)
        
            DateTime startDate;
            try
            
                startDate = DateTime.Parse(shipStatus);
                return true;
            
            catch
            
                return false;
            
        

【讨论】:

【参考方案5】:

@Brian Wells 谢谢,您的建议成功了,但并非完全... 适用于混合字段 int-string,但之后 datetime 列带有奇怪的字符,因此我在“黑客”。

1.- 执行 System.Io.File.Copy 并创建 excel 文件的副本。

2.- 在运行时以编程方式将日期时间列标题修改为日期时间格式,即“01/01/0001”。

3.- 保存 excel,然后将使用 HDR=NO 进行查询的技巧应用于修改后的文件。

很棘手,是的,但是有效,而且速度相当快,如果有人对此有任何替代方案,我会很高兴听到。

您好。

P.D.对不起,我的英语不是我的母语。

【讨论】:

没问题,很高兴它有帮助!我将发布我之前在这篇文章的另一个答案中使用过的 DateTime 分辨率(此处给出的字符不足)。【参考方案6】:

有两种方法可以处理混合数据类型和 excel。

方法一

打开您的 Excel 电子表格并手动将列格式设置为所需的格式。在本例中为“文本”。

方法二

有一个"hack" that consists of appending "IMEX=1" to your connection string 像这样:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=myfile.xls;Extended Properties=Excel 8.0;IMEX=1

这将尝试根据注册表中的设置方式处理混合 Excel 格式。这可以由您在本地设置,但对于服务器,这可能不是一个选项。

【讨论】:

它给出错误:System.Data.OleDb.OleDbException 未被用户代码处理 Message="Could not find installable ISAM." Source="Microsoft JET 数据库引擎" ErrorCode=-2147467259 我知道这个帖子已经过时了,但我遇到了和 Pratik 一样的问题...由于“找不到可安装的 ISAM”错误,我无法指定 IMEX=1 :( 为了避免可安装的 ISAM 错误,您需要在扩展属性内容周围添加引号。扩展属性="Excel8.0;IMEX=1" 我不敢相信我必须阅读这么多才能找到最明显的解决方案。我原以为这可能会奏效,但由于某种原因从未尝试过。这是最好的解决方案:Open up your excel spreadsheet and set the column format to the desired format manually. In this case, 'Text'. 我可能会发布一些代码,在 VBA 中自动执行此过程。

以上是关于OleDB 和混合 Excel 数据类型:缺少数据的主要内容,如果未能解决你的问题,请参考以下文章

C# OLEDB读取EXCEL的数据为空值

导出到excel相关问题

Excel、OleDb 和前导零

如何使用 oledb 在 c# 中将下拉列数据插入到 excel 中

使用 OleDb 和 Access 数据库引擎的 C# Excel 插入错误

SSIS Excel 数据导入 - 行中的混合数据类型