OleDB&混合的Excel数据类型:缺少数据

时间:2010-07-12 21:07:50

标签: excel datatable dataset oledb

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

我尝试将let OleDB handle everything by itself automatically读入数据集/数据表,但{Product}中的任何值(如n######)都会丢失,忽略并留空。我尝试通过使用datareader循环遍历每一行来手动创建我的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######这样的值。我怎样才能做到这一点?

6 个答案:

答案 0 :(得分:102)

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

据我所知,ADO.NET根据列中的大多数值选择数据类型(与数值数据类型相关联)。即,如果样本集中的大部分PartID都是数字,则ADO.NET会将该列声明为数字。因此,ADO.Net将尝试将每个单元格转换为数字,这对于“text”PartID值将失败,而不会导入那些“text”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")
                });

答案 1 :(得分:10)

我发现有几个论坛声称通过将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;
    }

答案 2 :(得分:6)

有两种方法可以处理混合数据类型&amp; Excel中。

方法1

  • 打开Excel电子表格并手动将列格式设置为所需的格式。在这种情况下,'文字'。

方法2

  • "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格式。这可以由您在本地设置,但对于服务器,这可能不是一个选项。

答案 3 :(得分:6)

没问题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;
            }
        }

答案 4 :(得分:1)

@Brian Wells谢谢你,你的建议做了诀窍,但并非完全......为混合字段int-string工作,但是datetime列之后带有奇怪的字符,所以我应用了一个“hack” “黑客”。

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

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

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

整蛊,是的,但工作,并且合理地快,如果有人有任何替代方案,我会很高兴听到。

问候。

Pd积。请原谅我的英语,这不是我的母语。

答案 5 :(得分:0)

快捷方式 - &gt;如果Excel中有混合类型列:将列Z排序为A

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

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

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