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

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

该文件在打开之前通过我们的file upload代码存储在临时目录中。 由于我们在IIS中启用了匿名访问,并且因为我们也在web.config中使用模拟,所以文件夹C:\ Windows \ Temp \具有Internet访客用户帐户(IUSR_ [MachineName])的适当权限,修改和删除那里的文件。

OleDb连接string:
Provider = Microsoft.Jet.OLEDB.4.0; Data Source = C:\ Windows \ Temp \ tmp123.tmp.xls;
Extended Properties =“Excel 8.0; HDR = Yes; IMEX = 1;”

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

exception的堆栈跟踪是:
     System.Exception:FileParsingFailed ---> System.Data.OleDb.OleDbException:  
    未指定的错误在  
     System.Data.OleDb.OleDbConnectionInternal..ctor(OleDbConnectionString constr,  
     OleDbConnection连接)  
     System.Data.OleDb.OleDbConnectionFactory.CreateConnection(DbConnectionOptions选项,   
    对象poolGroupProviderInfo,DbConnectionPool池,DbConnection拥有对象)在  
     System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(的DbConnection  
     owningConnection,DbConnectionPoolGroup poolGroup)  
     System.Data.ProviderBase.DbConnectionFactory.GetConnection(的DbConnection  
    拥有连接)  
     System.Data.ProviderBase.DbConnectionClosed.OpenConnection(的DbConnection  
     outerConnection,DbConnectionFactory connectionFactory)at  
     System.Data.OleDb.OleDbConnection.Open()  

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

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

我们的限制:
1.我们坚持使用MS Office 2003(.xls)格式,不能移动到MS Office 2007(OOXML)格式。
2.我们不使用CSV的原因是因为我们的数据中可能会有逗号(即使我们使用引用,这也是很痛苦的),而且我们也在电子表格中使用多个工作表(这不能用CSV来完成) 。

谢谢! 🙂

更新:
谢谢,基思。 这看起来像Jet引擎的问题,但我们使用它,因为缺乏( 免费和易于使用)的替代品。
谢谢,乔。 但是我们的预算有限 – 所以我们主要是在寻找免费的工具/库。

确保你正在closures你的连接。

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

我怀疑这个错误是与古老的Jet OLEDB引擎有关。 这是相当吱吱喳喳的 – 对大多数桌面的东西很好,但没有太多的用于企业数据交换。

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

这些文件实际上是拉链 – 将它们重命名为.zip,您可以查看里面的XML文件。

XML文件的格式是相当可怕的(例如单元格注释是VML,呃!),但可读。

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

我一直在使用SpreadSheetGear.NET一段时间,主要是为了创buildExcel文件,它运作良好。

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

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

基本版本用来注册Visual C ++ Express 2005免费。这是没有改变的,所以它可能会在2008版本中存在,也可能不存在。

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

免责声明:我自己的SpreadsheetGear LLC

下面是用SpreadsheetGear创build一个50,000行10列工作簿的代码,将其保存到磁盘,然后使用OleDb和SpreadsheetGear对数字进行求和。 SpreadsheetGear在0.31秒内读取500K单元格,而OleDB则为0.63秒 – 仅仅快了一倍。 SpreadsheetGear实际上创build并读取工作簿的时间比使用OleDB读取工作簿的时间要less。

代码如下。 你可以看到现场的样品或免费试用 。

 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; } } } 

我有同样的问题,它似乎是通过在循环的每个迭代closures文件(xls或csv)的连接修复。 我假设你也循环遍历一个文件列表,并打开()一个新的连接到每个文件。 如果你在循环结尾closures()连接,问题似乎就消失了。

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

似乎我错了,请参阅与OleDbConnection,Excel和连接池的问题

基本上CRice说的是什么,但是在使用Excel-ConnectionString调用OleDbDataAdapter (String, String)构造函数时,在执行Dispose()时似乎存在问题,因为隐式创build的连接显然未closures。

解决方法是将所有调用OleDbDataApater用法(您正在使用…东西,因为它实现了IDisposable )与单独

 using (var conn = new OleDbConnection(connectionString)) 

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

编辑:我是错误的连接closures处置。 conn.Dispose()不会closures连接,所以在using (var conn = new OleDbConnection(connectionString))你仍然需要做一个conn.Close()