【问题标题】:Can only read Excel file when it is actually open in Ms Excel只有在 Excel 女士中实际打开时才能读取 Excel 文件
【发布时间】:2017-10-17 02:49:56
【问题描述】:

我正在使用以下代码打开一个 Excel 文件 (XLS) 并使用第一个工作表填充 DataTable

var connectionString = string.Format("Provider=Microsoft.Jet.OLEDB.4.0; data source={0}; Extended Properties=Excel 8.0;", filename);

OleDbConnection connExcel = new OleDbConnection(connectionString);
connExcel.Open();
DataTable dtExcelSchema;
dtExcelSchema = connExcel.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
string SheetName = dtExcelSchema.Rows[0]["TABLE_NAME"].ToString();
connExcel.Close();

var adapter = new OleDbDataAdapter("SELECT * FROM [" + SheetName + "]", connectionString);
var ds = new DataSet();
int count = 0;

adapter.Fill(ds, SheetName);

DataTable dt = ds.Tables[0];

仅当文件已在 Ms Excel 中打开时才有效。为什么会这样?

如果文件未打开,我会收到一条错误消息(在线connExcel.Open):External table is not in the expected format.

【问题讨论】:

  • 尝试在工作表名称后加一个 $(在 " 和 ] 之间" 看看是否有效。
  • 你的fileName 是什么?那可能需要包括地址吗? (即fileName = "C:\Users\myself\Docs\mySheet.xlsx"
  • @CharlesMay 它在 Excel 打开时工作,所以这不是问题。无论如何,错误发生在代码的那部分之前。
  • @BruceWayne 文件名没问题,我查过了。
  • 这不可能是一个重命名为 .xls 的文本文件吧?我问是因为另一个 SO post 发现这是问题所在。

标签: c# excel


【解决方案1】:

我也面临同样的问题,因此对于这个网站,许多开发人员都在为此苦苦挣扎:

-When I try read Excel with OLE DB all values are empty

-Can't connect to excel file unless file is already open

其实我使用的是经典的连接字符串(注意我正在尝试读取一个 97/2003 文件):

Provider=Microsoft.Jet.OLEDB.4.0; Data Source = " + GetFilename(filename) + "; Extended Properties ='Excel 8.0;HDR=NO;IMEX=1'

但只有在以下情况下才能正确读取文件:

  • 在 Excel 中打开 ,甚至在 Word 中! (该文件当然看起来已损坏且无法读取,但随后 OleDb 程序可以读取文件的每一行),我没有尝试使用其他 Office 应用

  • 文件不是只读模式

我也尝试过手动锁定文件或用其他非办公应用程序打开,但结果不一样。如果我遵循前两个规则(在 Word 或 Excel 中以非只读模式打开文件)我可以看到所有单元格,否则似乎第一个 column 被完全忽略(所以 F2 变成了 F1, F3 变成了 F2,... 最后一个 F6 应该变成 F5 否则它会抛出和超出索引错误)。

为了在不使用第三方库的情况下保持与 OleDb 的兼容性,我发现了一个使用 Microsoft.Office.Interop.Excel 程序集的非常愚蠢的解决方法。

Excel.Application _app = new Excel.Application();
var workbooks = _app.Workbooks;
workbooks.Open(_filename);

// OleDb Connection
using (OleDbConnection conn = new OleDbConnection(connectionOleDb))
            {
                try
                {
                    conn.Open();
                    OleDbCommand cmd = new OleDbCommand();
                    cmd.Connection = conn;

                    cmd.CommandText = String.Format("SELECT * FROM [{0}$]", tableName);
OleDbDataReader myReader = cmd.ExecuteReader();

                    int i = 0;
                    while (myReader.Read())
                    {
                        //Here I read through all Excel rows
                    }


                }
                catch (Exception E)
                {
                    MessageBox.Show("Error!\n" + E.Message);
                }
                finally
                {
                    conn.Close();

                    workbooks.Close();
                    if (workbooks != null)
                        System.Runtime.InteropServices.Marshal.ReleaseComObject(workbooks);
                    _app.Quit();
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(_app);
                }
            }

基本上前 3 行运行一个 Excel 实例,该实例持续的时间正好是 OleDb 执行其任务所需的时间。 最后 4 行,在 finally 块内,让 Excel 实例在任务结束后立即正确关闭,避免出现幽灵 Excel 进程。

我再说一遍,这是一个非常愚蠢的解决方法,还需要将 1.5 MB 的 dll (Microsoft.Office.Interop.Excel.dll) 添加到项目中。 无论如何,OleDb 无法自行管理丢失的数据似乎是不可能的......

【讨论】:

  • 对解决方法投了赞成票,但在有解决方案之前不能接受答案
  • 我当然不会放弃! :-) 我刚刚发现原始文件生成器对最终结果的影响很大:官方 Excel 应用程序使文件可靠且易于阅读,第三方应用程序使事情变得更加棘手
【解决方案2】:

我遇到了同样的问题。如果文件已打开,则读取正常,但如果文件已关闭……有些事情很奇怪……在我的情况下,我从列和值中收到了奇怪的数据……调试我找到了第一张工作表的名称并且很奇怪["xls _xlnm#_FilterDatabase"] 在互联网上查找我发现这是一个隐藏表的名称和避免阅读此表的技巧(HERE),所以我实现了一个方法:

private string getFirstVisibileSheet(DataTable dtSheet, int index = 0)
{
    string sheetName = String.Empty;
    if (dtSheet.Rows.Count >= (index + 1))
    {
        sheetName = dtSheet.Rows[index]["TABLE_NAME"].ToString();
        if (sheetName.Contains("FilterDatabase"))
        {
            return getFirstVisibileSheet(dtSheet, ++index);
        }
    }
    return sheetName;
}

对我来说效果很好。

我的完整示例代码是:

string excelFilePath = String.Empty;
string stringConnection = String.Empty;

using (OpenFileDialog openExcelDialog = new OpenFileDialog())
{
    openExcelDialog.Filter = "Excel 2007 (*.xlsx)|*.xlsx|Excel 2003 (*.xls)|*.xls";
    openExcelDialog.FilterIndex = 1;
    openExcelDialog.RestoreDirectory = true;
    DialogResult windowsResult = openExcelDialog.ShowDialog();
    if (windowsResult != System.Windows.Forms.DialogResult.OK)
    {
        return;
    }

    excelFilePath = openExcelDialog.FileName;
    using (DataTable dt = new DataTable())
    {
        try
        {
            if (!excelFilePath.Equals(String.Empty))
            {
                stringConnection = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + excelFilePath + ";Extended Properties='Excel 8.0; HDR=YES;';";
                using (OleDbConnection conn = new OleDbConnection(stringConnection))
                {
                    conn.Open();
                    OleDbCommand cmd = new OleDbCommand();
                    cmd.Connection = conn;

                    DataTable dtSheet = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);

                    string sheetName = getFirstVisibileSheet(dtSheet);

                    cmd.CommandText = "SELECT * FROM [" + sheetName + "]";
                    dt.TableName = sheetName;

                    OleDbDataAdapter da = new OleDbDataAdapter(cmd);
                    da.Fill(dt);

                    cmd = null;
                    conn.Close();
                }
            }

            //Read and Use my DT
            foreach (DataRow row in dt.Rows)
            {
                //On my case I need data on first and second Columns
                if ((row.ItemArray.Count() < 2) ||
                    (row[0] == null || String.IsNullOrWhiteSpace(row[0].ToString()))
                    ||
                    (row[1] == null ||String.IsNullOrWhiteSpace(row[1].ToString())))
                {
                    continue;
                }

                //Get the number from the first COL
                int colOneNumber = 0;
                Int32.TryParse(row[0].ToString(), out colOneNumber);

                //Get the string from the second COL
                string colTwoString = row[1].ToString();

                //Get the string from third COL if is a file path valid
                string colThree = (row.ItemArray.Count() >= 3 
                                && !row.IsNull(2) 
                                && !String.IsNullOrWhiteSpace(row[2].ToString()) 
                                && File.Exists(row[2].ToString())
                                ) ? row[2].ToString() : String.Empty;
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("Import error.\n" + ex.Message, "::ERROR::", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

private string getFirstVisibileSheet(DataTable dtSheet, int index = 0)
{
    string sheetName = String.Empty;
    if (dtSheet.Rows.Count >= (index + 1))
    {
        sheetName = dtSheet.Rows[index]["TABLE_NAME"].ToString();
        if (sheetName.Contains("FilterDatabase"))
        {
            return getFirstVisibileSheet(dtSheet, ++index);
        }
    }
    return sheetName;
}

【讨论】:

    【解决方案3】:

    它在ToString() 上失败了吗,就像这里一样? Error is "Object reference not set to an instance of an object"

    Convert.ToString() 能解决什么问题吗?

    【讨论】:

    • connExcel.Open 上失败
    猜你喜欢
    • 1970-01-01
    • 2021-12-21
    • 2019-01-05
    • 2012-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多