C#/ ASP.NET Oledb - MS Excel读取“未指定的错误”

时间:2009-01-23 06:31:58

标签: c# asp.net excel exception oledb

我们在Windows Server 2003 Enterprise Edition上的IIS 6上运行了一个C#/ ASP.NET(2.0)应用程序。此应用程序使用OleDb读取Excel文件,但有些情况下我们从应用程序中抛出“未指定的错误”异常。

在打开之前,文件由我们的文件上传代码存储在临时目录中。由于我们在IIS中启用了匿名访问,并且因为我们还在web.config中使用模拟,因此文件夹C:\ Windows \ Temp \具有能够创建的Internet来宾用户帐户(IUSR_ [MachineName])的适当权限,修改和删除那里的文件。

OleDb连接字符串:
Provider = Microsoft.Jet.OLEDB.4.0;数据源= C:\ Windows \ Temp \ tmp123.tmp.xls;
扩展属性=“Excel 8.0; HDR =是; IMEX = 1;”

[上面的“数据源”属性会因每个文件而改变。]

The stack trace of the exception is:
    System.Exception: FileParsingFailed ---> System.Data.OleDb.OleDbException:  
    Unspecified error at  
    System.Data.OleDb.OleDbConnectionInternal..ctor(OleDbConnectionString constr,  
    OleDbConnection connection) at  
    System.Data.OleDb.OleDbConnectionFactory.CreateConnection(DbConnectionOptions options,   
    Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningObject) at  
    System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(DbConnection  
    owningConnection, DbConnectionPoolGroup poolGroup) at  
    System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection  
    owningConnection) at  
    System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection  
    outerConnection, DbConnectionFactory connectionFactory) at  
    System.Data.OleDb.OleDbConnection.Open()  

解决方法:
到目前为止,我们可以提出的唯一解决方法是执行iisreset(我们还将应用程序池回收配置为每天在IIS中执行一次,但它似乎没有帮助,因为问题有时会连续几天持续存在)。虽然这不是一件好事,但更糟糕的是,我们在同一网站上有其他应用程序,每当我们重置IIS时都会受到影响。

问题:
1.我们如何解决这个错误,因为它偶尔发生,我们没有看到一个模式? 2.除了OleDb之外,还有更好(和免费)的方法来处理来自C#/ ASP.NET的Excel文件吗? (我们不希望在服务器上安装MS Office,因为它不是微软推荐的)

我们的限制:
1.我们坚持使用MS Office 2003(.xls)格式,无法转换为MS Office 2007(OOXML)格式。
2.我们不使用CSV的原因是因为我们的数据中可能有逗号(即使我们使用引用也很难处理),我们在电子表格中也使用了多个工作表(这不能用CSV完成) 。

谢谢! :)

更新:
谢谢,基思。这似乎与Jet引擎有关,但我们使用它是因为缺少(免费和易于使用)替代品。
谢谢,乔。但我们的预算有限 - 所以我们主要是寻找免费的工具/库。

7 个答案:

答案 0 :(得分:3)

确保关闭连接。

例如,在开发MS Access应用程序(Jet)时,如果打开的连接太多,则会发生此错误。它工作正常(偶尔有你的),直到达到最大开放连接。

答案 1 :(得分:2)

我怀疑这个错误与古老的Jet OLEDB引擎有关。它相当吱吱作响 - 适用于大多数桌面设备,但对企业数据交换没有多大用处。

如果您可以升级到最新的C#3 / .Net 3.5,则可以使用System.IO.Packaging库打开Office 2007文件(.xlsx或.xlsm文件)。

这些文件实际上是拉链 - 将它们重命名为.zip,您只需查看其中的XML文件。

XML文件的格式非常糟糕(例如单元格注释是VML,呃!)但是可读。

或者让您的用户将Excel表保存为CSV。我会避免使用Microsoft文本驱动程序数据库提供程序 - 它是垃圾并且无法处理unicode。无论如何,CSV都很容易阅读。

答案 2 :(得分:2)

我一直在使用SpreadSheetGear.NET一段时间,主要是创建Excel文件,而且效果很好。

http://www.spreadsheetgear.com/products/spreadsheetgear.net.aspx

它提供本机.NET中的二进制Excel文件读取/写入,解决了我在尝试使用OLE和JET读取和创建Excel文件时遇到的所有先前问题。

以前用作注册Visual C ++ Express 2005的免费基本版本。这是未经公开的,所以它可能会或可能不会存在于2008版本中。

答案 3 :(得分:1)

SpreadsheetGear for .NET为您提供了一个用于处理.NET中的xls和xlsx工作簿的API。它比OleDB或Excel COM对象模型更容易使用和更快(请继续阅读以获得一些证据)。

免责声明:我拥有SpreadsheetGear LLC

下面是使用SpreadsheetGear创建50,000行10列工作簿的代码,将其保存到磁盘,然后使用OleDb和SpreadsheetGear对数字求和。 SpreadsheetGear在0.31秒内读取500K单元,而使用OleDB则读取0.63秒 - 仅快两倍。 SpreadsheetGear实际上创建和读取工作簿的时间比用OleDB读取工作簿的时间短。

代码如下。您可以查看live samples或使用free trial自行尝试。

using System;
using System.Data; 
using System.Data.OleDb; 
using SpreadsheetGear;
using SpreadsheetGear.Advanced.Cells;
using System.Diagnostics;

namespace SpreadsheetGearAndOleDBBenchmark
{
    class Program
    {
        static void Main(string[] args)
        {
            // Warm up (get the code JITed).
            BM(10, 10);

            // Do it for real.
            BM(50000, 10);
        }

        static void BM(int rows, int cols)
        {
            // Compare the performance of OleDB to SpreadsheetGear for reading
            // workbooks. We sum numbers just to have something to do.
            //
            // Run on Windows Vista 32 bit, Visual Studio 2008, Release Build,
            // Run Without Debugger:
            //  Create time: 0.25 seconds
            //  OleDb Time: 0.63 seconds
            //  SpreadsheetGear Time: 0.31 seconds
            //
            // SpreadsheetGear is more than twice as fast at reading. Furthermore,
            // SpreadsheetGear can create the file and read it faster than OleDB
            // can just read it.
            string filename = @"C:\tmp\SpreadsheetGearOleDbBenchmark.xls";
            Console.WriteLine("\nCreating {0} rows x {1} columns", rows, cols);
            Stopwatch timer = Stopwatch.StartNew();
            double createSum = CreateWorkbook(filename, rows, cols);
            double createTime = timer.Elapsed.TotalSeconds;
            Console.WriteLine("Create sum of {0} took {1} seconds.", createSum, createTime);
            timer = Stopwatch.StartNew();
            double oleDbSum = ReadWithOleDB(filename);
            double oleDbTime = timer.Elapsed.TotalSeconds;
            Console.WriteLine("OleDb sum of {0} took {1} seconds.", oleDbSum, oleDbTime);
            timer = Stopwatch.StartNew();
            double spreadsheetGearSum = ReadWithSpreadsheetGear(filename);
            double spreadsheetGearTime = timer.Elapsed.TotalSeconds;
            Console.WriteLine("SpreadsheetGear sum of {0} took {1} seconds.", spreadsheetGearSum, spreadsheetGearTime);
        }

        static double CreateWorkbook(string filename, int rows, int cols)
        {
            IWorkbook workbook = Factory.GetWorkbook();
            IWorksheet worksheet = workbook.Worksheets[0];
            IValues values = (IValues)worksheet;
            double sum = 0.0;
            Random rand = new Random();
            // Put labels in the first row.
            foreach (IRange cell in worksheet.Cells[0, 0, 0, cols - 1])
                cell.Value = "Cell-" + cell.Address;
            // Using IRange and foreach would be less code, 
            // but we'll do it the fast way.
            for (int row = 1; row <= rows; row++)
            {
                for (int col = 0; col < cols; col++)
                {
                    double number = rand.NextDouble();
                    sum += number;
                    values.SetNumber(row, col, number);
                }
            }
            workbook.SaveAs(filename, FileFormat.Excel8);
            return sum;
        }

        static double ReadWithSpreadsheetGear(string filename)
        {
            IWorkbook workbook = Factory.GetWorkbook(filename);
            IWorksheet worksheet = workbook.Worksheets[0];
            IValues values = (IValues)worksheet;
            IRange usedRahge = worksheet.UsedRange;
            int rowCount = usedRahge.RowCount;
            int colCount = usedRahge.ColumnCount;
            double sum = 0.0;
            // We could use foreach (IRange cell in usedRange) for cleaner 
            // code, but this is faster.
            for (int row = 1; row <= rowCount; row++)
            {
                for (int col = 0; col < colCount; col++)
                {
                    IValue value = values[row, col];
                    if (value != null && value.Type == SpreadsheetGear.Advanced.Cells.ValueType.Number)
                        sum += value.Number;
                }
            }
            return sum;
        }

        static double ReadWithOleDB(string filename)
        {
            String connectionString =  
                "Provider=Microsoft.Jet.OLEDB.4.0;" + 
                "Data Source=" + filename + ";" + 
                "Extended Properties=Excel 8.0;"; 
            OleDbConnection connection = new OleDbConnection(connectionString); 
            connection.Open(); 
            OleDbCommand selectCommand =new OleDbCommand("SELECT * FROM [Sheet1$]", connection); 
            OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); 
            dataAdapter.SelectCommand = selectCommand; 
            DataSet dataSet = new DataSet(); 
            dataAdapter.Fill(dataSet); 
            connection.Close(); 
            double sum = 0.0;
            // We'll make some assumptions for brevity of the code.
            DataTable dataTable = dataSet.Tables[0];
            int cols = dataTable.Columns.Count;
            foreach (DataRow row in dataTable.Rows)
            {
                for (int i = 0; i < cols; i++)
                {
                    object val = row[i];
                    if (val is double)
                        sum += (double)val;
                }
            }
            return sum;
        }
    }
}

答案 4 :(得分:1)

我有同样的问题,似乎通过在循环的每次迭代中关闭与文件(xls或csv)的连接来修复它。我假设你也循环遍历文件列表和.Open()一个到每个文件的新连接。如果你。在循环结束时关闭()连接,问题似乎就消失了。

答案 5 :(得分:1)

连接TimeOut可能是其中一个原因。通过调试检查查询在应用程序中执行多长时间。

答案 6 :(得分:1)

好像我弄错了,请参阅Problem with OleDbConnection, Excel and connection pooling

基本上是CRice所说的,但是当使用Excel-ConnectionString调用Dispose()构造函数时,OleDbDataAdapter (String, String)的实现似乎存在问题,因为隐含创建的连接显然不是闭合。

<击> 解决方法是将所有调用OleDbDataApater用法(你正在使用......实现IDisposable的东西)包装成一个单独的

using (var conn = new OleDbConnection(connectionString))

然后调用OleDbDataAdapter (String, OleDbConnection)构造函数。

<击> 修改 连接关闭处理时我错了。 conn.Dispose()未关闭连接,因此在using (var conn = new OleDbConnection(connectionString))内您仍需要执行conn.Close()