C # / ASP.NET Oledb - MS Excel прочитал «Неуказанная ошибка» - PullRequest
5 голосов
/ 23 января 2009

У нас есть приложение C # / ASP.NET (2.0), работающее на IIS 6 в Windows Server 2003 Enterprise Edition. Это приложение читает файлы Excel, используя OleDb, но бывают случаи, когда мы получаем исключение "Unspecified Error" из приложения.

Файл сохраняется во временном каталоге нашим кодом загрузки файла перед открытием. Поскольку в IIS разрешен анонимный доступ и мы также используем олицетворение в web.config, папка C: \ Windows \ Temp \ имеет соответствующие разрешения для учетной записи гостя в Интернете (IUSR_ [MachineName]), которую можно создать, изменить и удалить файлы там.

Строка подключения OleDb:
Поставщик = 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. Существуют ли более эффективные (и бесплатные) способы обработки файлов Excel из C # / ASP.NET, кроме OleDb? (Мы предпочитаем не устанавливать MS Office на серверах, так как это не рекомендуется Microsoft)

Наши ограничения:
1. Мы застряли в формате MS Office 2003 (.xls) и не можем перейти в формат MS Office 2007 (OOXML).
2. Причины, по которым мы не используем CSV, заключаются в том, что у нас могут быть запятые в наших данных (с этим трудно справиться, даже если мы используем цитирование), и мы также используем несколько таблиц в нашей электронной таблице (этого нельзя сделать с помощью CSV) ,

Спасибо! :)

Обновление:
Спасибо Кит. Это кажется проблемой с движком Jet, но мы используем его из-за отсутствия ( free и простых в использовании) альтернатив.
Спасибо, Джо. Но у нас ограниченный бюджет - поэтому мы в основном ищем бесплатные инструменты / библиотеки.

Ответы [ 7 ]

3 голосов
/ 29 января 2009

Убедитесь, что вы закрываете свои соединения.

Например, при разработке приложений MS Access (Jet) эта ошибка возникает, если открыто слишком много соединений. Некоторое время он работает нормально (иногда это происходит), пока не достигнет максимально открытых соединений.

2 голосов
/ 19 февраля 2009

Я уже некоторое время использую SpreadSheetGear.NET, в основном для создания файлов Excel, и он хорошо работает.

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

Он обеспечивает чтение / запись двоичных файлов Excel в собственном .NET, решая все предыдущие проблемы, с которыми я столкнулся при попытке использовать OLE и JET для чтения и создания файлов Excel.

Базовая версия, используемая для бесплатной регистрации в Visual C ++ Express 2005. Это было без рекламы, поэтому она может существовать или не существовать в версии 2008 года.

2 голосов
/ 23 января 2009

Я подозреваю, что ошибка связана с почтенным двигателем Jet OLEDB. Он довольно скрипучий - отлично подходит для большинства настольных систем, но не очень полезен для обмена корпоративными данными.

Если вы можете выполнить обновление до последней версии C # 3 / .Net 3.5, вы можете использовать библиотеку System.IO.Packaging для открытия файлов Office 2007 (файлы .xlsx или .xlsm).

Эти файлы на самом деле являются почтовыми индексами - переименуйте их в .zip, и вы сможете просто просмотреть файлы XML внутри.

Форматы файлов XML довольно ужасные (например, комментарии к ячейкам VML, тьфу!), Но читаемые.

Или попросите пользователей сохранить таблицы Excel в формате CSV. Хотя я бы избегал поставщика БД текстового драйвера Microsoft - это мусор и не может обрабатывать юникод. CSV легко читать в любом случае.

1 голос
/ 11 апреля 2011

Похоже, я ошибся, см. Проблема с OleDbConnection, Excel и пулами соединений

В основном то, что сказал CRice, но, похоже, существует проблема в реализации Dispose(), когда конструктор OleDbDataAdapter (String, String) вызывается с Excel-ConnectionString, поскольку неявно созданное соединение, очевидно, не закрыто.

Обходной путь - обернуть все вызовы OleDbDataApater использования (вы делали использование ... вещи, поскольку он реализует IDisposable) с отдельным

using (var conn = new OleDbConnection(connectionString))

, а затем вызовите конструктор OleDbDataAdapter (String, OleDbConnection).

EDIT: Я был не прав насчет закрытия соединения при утилизации. conn.Dispose() не закрывает соединение, поэтому внутри using (var conn = new OleDbConnection(connectionString)) вам все равно нужно сделать conn.Close().

1 голос
/ 08 марта 2011

Время подключения может быть одной из причин. Проверьте запрос, как много времени занимает отработка в приложении путем отладки.

1 голос
/ 02 декабря 2009

У меня была такая же проблема, и кажется, что она исправлена ​​путем закрытия соединения с файлом (xls или csv) на каждой итерации цикла Я предполагаю, что вы также просматриваете список файлов и .Open () новое соединение с каждым файлом. Если вы закрываете () соединение в конце цикла, проблема, похоже, исчезнет.

1 голос
/ 23 февраля 2009

SpreadsheetGear for .NET предоставляет API для работы с книгами xls и xlsx из .NET. Он проще в использовании и быстрее, чем OleDB или объектная модель Excel COM (продолжайте читать, чтобы убедиться в этом).

Отказ от ответственности: я владею SpreadsheetGear LLC

Ниже приведен код для создания книги размером 50 000 строк на 10 столбцов с помощью SpreadsheetGear, сохранения ее на диске и суммирования чисел с помощью OleDb и SpreadsheetGear. SpreadsheetGear считывает 500К ячеек за 0,31 секунды по сравнению с 0,63 секунды с OleDB - чуть более чем в два раза быстрее. SpreadsheetGear фактически создает и читает книгу за меньшее время, чем требуется для чтения книги с помощью OleDB.

Код ниже. Вы можете посмотреть живые образцы или попробовать сами с бесплатной пробной версией .

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