2

This was working..and I moved the disposal code to the finally block, and now it fails every time.

I have a test spreadsheet with 4 records, 6 columns long. Here is the code I'm using to bring it in. This is ASP .Net 3.5 on IIS 5 (my pc) and on IIS 6 (web server).

It blows up on the line right before the catch: "values = (object[,])range.Value2;" with the following error:

11/2/2009 8:47:43 AM :: Not enough storage is available to complete this operation. (Exception from HRESULT: 0x8007000E (E_OUTOFMEMORY))

Any ideas? Suggestions? I got most of this code off codeproject, so I have no idea if this is the correct way to work with Excel. Thanks for any help you can provide.

Here is my code:

Excel.ApplicationClass app = null;
Excel.Workbook book = null;
Excel.Worksheet sheet = null;
Excel.Range range = null;

object[,] values = null;

try
{
    // Configure Excel
    app = new Excel.ApplicationClass();
    app.Visible = false;
    app.ScreenUpdating = false;
    app.DisplayAlerts = false;

    // Open a new instance of excel with the uploaded file
    book = app.Workbooks.Open(path);

    // Get first worksheet in book
    sheet = (Excel.Worksheet)book.Worksheets[1];

    // Start with first cell on second row
    range = sheet.get_Range("A2", Missing.Value);

    // Get all cells to the right
    range = range.get_End(Excel.XlDirection.xlToRight);

    // Get all cells downwards
    range = range.get_End(Excel.XlDirection.xlDown);

    // Get address of bottom rightmost cell
    string downAddress = range.get_Address(false, false, Excel.XlReferenceStyle.xlA1, Type.Missing, Type.Missing);

    // Get complete range of data
    range = sheet.get_Range("A2", downAddress);

    // get 2d array of all data
    values = (object[,])range.Value2;
}
catch (Exception e)
{
    LoggingService.log(e.Message);
}
finally
{
    // Clean up
    range = null;
    sheet = null;

    if (book != null)
        book.Close(false, Missing.Value, Missing.Value);

    book = null;

    if (app != null)
        app.Quit();

    app = null;
}

return values;
IronicMuffin
  • 4,182
  • 12
  • 47
  • 90
  • 3
    Working with Excel from ASP.NET is asking for trouble. The COM components were not designed to work in a multithreaded environment. – RichardOD Nov 02 '09 at 18:58
  • Or any server environment for that matter. Office interop is office macros on steriods - they *can* be used for programmable access to office documents, but it's not a very robust way of doing it. – David Nov 02 '09 at 19:01
  • Fair enough. This may require another question, but how would you go about letting a user upload a .xls document get the values from it? I wouldn't think it'd be that hard to turn it into a flat file... – IronicMuffin Nov 02 '09 at 19:03
  • @IronMuffin- this is easily achievable using ADO.NET and OleDb- my internet is a bit bad atm, but this should give you some idea- http://www.davidhayden.com/blog/dave/archive/2006/05/26/2973.aspx – RichardOD Nov 02 '09 at 19:06
  • @RichardOD do you still know the title of that post? The link returns 404 now. – Jeroen Wiert Pluimers Sep 18 '12 at 08:53
  • 1
    @Jeroen Wiert Pluimers. Unfortunately not- this might be useful though: http://support.microsoft.com/kb/316934/en-us#12 – RichardOD Sep 28 '12 at 22:17

4 Answers4

5

I'm not sure if this is your issue or not, but it very well may be. You are not cleaning up your excel objects properly. They are unmanaged code and can be tricky to clean up. Finally should look something like this: And as the comments have noted working with excel from asp.net is not a good idea. This cleanup code is from a winform app:

 GC.Collect();
 GC.WaitForPendingFinalizers();


 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(range);
 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sheet);
 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(book);

 WB.Close(false, Type.Missing, Type.Missing);

 Excel.Quit();
 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(Excel);

EDIT

An alternative would be to use ado.net to open the workbook.

            DataTable dt = new DataTable();

            string connectionString;
            System.Data.OleDb.OleDbConnection excelConnection;
            System.Data.OleDb.OleDbDataAdapter da;
            DataTable dbSchema;
            string firstSheetName;
            string strSQL;

            connectionString = @"provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filename + @";Extended Properties=""Excel 12.0;HDR=YES;IMEX=1""";
            excelConnection = new System.Data.OleDb.OleDbConnection(connectionString);
            excelConnection.Open();
            dbSchema = excelConnection.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null);
            firstSheetName = dbSchema.Rows[0]["TABLE_NAME"].ToString();
            strSQL = "SELECT * FROM [" + firstSheetName + "]";
            da = new OleDbDataAdapter(strSQL, excelConnection);
            da.Fill(dt);

            da.Dispose();
            excelConnection.Close();
            excelConnection.Dispose();
Gratzy
  • 9,164
  • 4
  • 30
  • 45
  • +1 for the suggestion to use ADO.NET. I would update your answer with a link on how to do this OleDb (there are lots out there). – RichardOD Nov 02 '09 at 19:07
  • Thanks, I added a simple sample – Gratzy Nov 02 '09 at 19:07
  • Thanks, I'll give this a shot. Anything to avoid interop makes me happy. I assume the providers are installed with office? I have to make sure these are present on our prod box. Regarding the cleanup, I tried adding the garbage collection, but did not add the FinalRelease calls. I will try those first to see if they get the job done. – IronicMuffin Nov 02 '09 at 19:20
  • You are better off with ado.net If all you need to do is retrieve data and not update I would go that route. Also the connection string is for excel 2007 – Gratzy Nov 02 '09 at 19:31
  • Yeah I see that. I have the 2000 connection string somewhere from a different project. Thanks again. – IronicMuffin Nov 02 '09 at 19:33
1

Creating/destroying excel on every request will have absolutely terrible performance to matter what you do. In general running any Office app using automation is a nasty business for a lot of reasons (see here). The only way I got it to work is to have a single instance of the app (in my case Word) which is initialized once, and then requests are queued to this instance for processing

If you can stay away from the apps and parse the file yourself (using MS libraries, of just XML)

mfeingold
  • 7,094
  • 4
  • 37
  • 43
  • The requests are few and far between, the app is in house and the feature is an extra... I don't mind the performance hit as long as it works. Thank you for the article, it was definitely eye-opening...I may just ditch the interop and go with the ADO solution above. – IronicMuffin Nov 02 '09 at 19:27
  • +1 indeed, keeping away from the Interop and automation is a good move. Later versions of windows using Office 2007 simply do not work without hacks to the OS which isn't realistic for a commercial app. Checkout my post http://stackoverflow.com/questions/1273116/reading-excel-files-as-a-server-process for a summary of 3rd party libraries to do the job for you. – Ian Nov 03 '09 at 10:39
1

You're going to run into a lot of trouble using interop from ASP.NET. Unless this is meant for some tiny in house application it would be advisable not to go forward with it.

Office Interop is not a programming API in the traditional sense - it's the Office macro system taken to its maximum, with the ability to work interprocess - for example an Excel macro could interact with Outlook.

Some consequences of using interop are:

  • You are actually opening a full copy of the office application.
  • Your actions are being executed by the application as if a user initiated them - that means instead of error messages being returned in code, they are displayed in the GUI.
  • Your copy of the application only closes if you explicitly command it - and even then errors can prevent that from actually happening (the application may present a "do you want to save" dialog if you did not programmably tell Excel that the changes do not need to be saved). This usually results in many hidden copies of Excel being left running on the system - open task manager and see how many excel.exe processes are running.

All of this makes interop something to avoid for regular desktop application, and something that should only be used as a last resort for server applications, since a GUI popup requiring action or script that leaks process is murder on a server environment.

Some alternatives include:

  • Using Microsoft Office 2007 XML based formats, so that you can write the XML files yourself.
  • Using SpreadsheetGear.Net, which is a .NET binary Excel file reader/writer (you don't need Excel installed, as it is completely stand alone). SpreadsheetGear models itself after the Intertop interfaces to make conversion of older code easier.
David
  • 24,700
  • 8
  • 63
  • 83
  • I am aware that it opens the app and processes it as mentioned. This is a tiny in-house app, so efficiency isn't really the goal. I just need to add the feature so they can use it if needed. I'd love to use '07, but we are stuck on '00 here. I will take a look at the link you provided. Thanks. – IronicMuffin Nov 02 '09 at 19:23
0

The error is probably exactly what it says, you are getting an out of memory error. Try to split the loading of the values array into several smaller chunks instead of getting the entire Range at one time. I tried your code out in C# and had no issues, but my spreadsheet I was loading was mostly empty.

I noticed that the Range was the entire spreadsheet though (from A2 to IV65536 or something). I'm not sure if that is intended.

One thing you could try using is sheet.UsedRange, that will cut down on the number of cells you are loading.

A couple additional small things that I have learned which you may find useful:

  • Use Application instead of ApplicationClass
  • Use Marshal.FinalReleaseComObject(range) (and also for sheet, book, app) otherwise you will have your EXCEL.EXE process sticking around.
  • Fair enough on the OOM error...I just find it hard to believe since there are really only 24 cells being imported... it is definitely not my intention to bring in all 65536 rows and columns...that looks like my bug right there. Thanks. – IronicMuffin Nov 02 '09 at 19:24