understanding COM exception - c#

Hey I'm getting the following exception:
Retrieving the COM class factory for component with CLSID {00020819-0000-0000-C000-000000000046} failed due to the following error: 80040154.
Office 2010.
I've read a bunch of results and the typical solution is change debug from AnyCPU to X86.(I don't want to do this because it's a large project, but it didn't work anyway). I want to understand the exception as well.
I was simply attempting to add a new workbook worksheet and fill it with some data, but it errs out on the creation of the Workbook due to the above exception.
using excel = Microsoft.Office.Interop.Excel;
public static void ExcelFunction()
{
excel.Workbook wb_XLS = new excel.Workbook();
excel.Worksheet ws_XLS = new excel.Worksheet();
ws_XLS = (excel.Worksheet)wb_XLS.ActiveSheet;
int x, y,count;
count = x = y = 0;
while (x < 100)
{
while (y < 100)
{
ws_XLS.Cells[x, y] = count.ToString();
count++;
y++;
}
x++;
}
}

Shouldn't you first create excel application and then create a new workbook by calling Add on workbooks collection rather than calling new on excel.workbook?
Excel.Application app = new ... ;
var workbook = app.Workbooks.Add();

Related

How to release an Excel Object

In my code below I have a commented out foreach loop that works except it does not release the excel object due to the background enum. So I am trying to convert it to a for loop but up until this point I have avoided 'for' loops and I can't seem to get mine to work. I am just pointing out that I know it is broke.
I did comment out the broken 'for' loop and it runs and all works as expected except I can't release the excel objects...
My question, I have tried so many different examples but I can not release the excel object from my task manager and it remains open in the back ground.
I use the "One Dot" rule
I'm using a try/final
I have tried many different Marshal.ReleaseComOjbect(obj), examples from the web
I am using a 'for loop' (not a foreach loop) so no hidden Enum object
Thank you for any assistance, education
My code:
public static List<ExcelObject> listOfExcelObjects = new List<ExcelObject>();
public static void txtSearchBar_Changed(object sender, EventArgs e)
{
Excel.Application excel = null;
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets sheets = null;
Excel.Worksheet worksheet = null;
Excel.Range range = null;
try
{
excel = new Excel.Application();
workbooks = excel.Workbooks;
workbook = workbooks.Open(GlobalObject.filePath, Notify: false, ReadOnly: true);
sheets = workbook.Sheets;
for (int i = 1; i < sheets.Count; i++)
{
worksheet = sheets.Item[i];
if (worksheet.Visible == Excel.XlSheetVisibility.xlSheetVisible)
{
Form_MainForm.thisForm.cmbx_WorkSheet.Items.Add(sheets.Item[i].Name);
if (worksheet.Name == "UNIT FEEDER")
{
Form_MainForm.thisForm.cmbx_WorkSheet.Text = worksheet.Name;
worksheet.Select();
Form_MainForm.thisForm.txtBox_StartingRow.Text = $"11";
Form_MainForm.thisForm.txtBox_EndingRow.Text = $"{_Events.EndOfRow()}";
range = worksheet.Range[$"A{Int32.Parse(Form_MainForm.thisForm.txtBox_StartingRow.Text)}", $"A{Int32.Parse(_Events.EndOfRow())}"];
for (int j = 1; j < range.Cells.Count; j++)
{
_Events.listOfExcelObjects.Add(new ExcelObject() { FeederNumber = range.Cells.Item[j] });
}
//foreach (Excel.Range j in worksheet.Range[$"A{Form_MainForm.thisForm.txtBox_StartingRow.Text}", $"A{_Events.EndOfRow()}"].Cells)
//{
// _Events.listOfExcelObjects.Add(new ExcelObject() { FeederNumber = j.Value });
//}
Form_MainForm.thisForm.grd_DataGridView.DataSource = _Events.listOfExcelObjects;
}
}
}
}
finally
{
releaseObject(range);
releaseObject(worksheet);
releaseObject(sheets);
releaseObject(workbook);
releaseObject(workbooks);
releaseObject(excel);
//Marshal.ReleaseComObject(range);
//Marshal.ReleaseComObject(worksheet);
//Marshal.ReleaseComObject(sheets);
//Marshal.ReleaseComObject(workbook);
//Marshal.ReleaseComObject(workbooks);
//Marshal.ReleaseComObject(excel);
}
}
private static void releaseObject(object obj)
{
if (obj != null && Marshal.IsComObject(obj))
{
Marshal.ReleaseComObject(obj);
}
obj = null;
}
You need to close Excel:
excel.Quit();
For those who don't read the comment section 'such as myself'
I have a function EndOfRow() that I did not do any cleaning to release objects. I didn't know about releasing objects when I created this function and I forgot about it. Many hours spent on a simple oversight.
Leaving out the Quit() was also an oversight in my many attemts to resolve my issue but was not the main problem, EndOfRow() was...
So what I learned is to release the objects as soon as I am done using excel.
Thanks Terry

Writing to sheet leads to "name already taken"

I'm learning how to use Interop.Excel. The test Winforms program reads an existing Excel file, checks if a tab names "Added_by_program" exists, deletes the sheet if it does, and creates a new sheet named "Added_by_program." If I don't try to write to the new sheet, the program runs perfectly, over and over. I get problems when I try to write to it. If the sheet is not present in the original file, the program runs perfectly one time, and writes correctly to the newly created sheet. but on subsequent runs, I get:
"System.Runtime.InteropServices.COMException: 'That name is already taken. Try a different one.'"
for the line that tries to name the new sheet. I have to manually kill the open Excel instance. What am I missing?
Code (irrelevant lines taken out)
using System;
using System.IO;
using System.Windows.Forms;
using Microsoft.Office.Interop.Excel;
namespace excelReadWrite
{
public partial class Form1 : Form
{
string readFolder = myPath;
string inFileName = #"Aram test excel file.xlsx";
string newSheetName = "Added_by_program";
Range rawRange = null;
Range pasteRange = null;
int rawCols = 0;
int rawRows = 0;
int iInSheet = 0;
int iNewSheet = 0;
int nInSheets = 0;
bool foundRawSheet = false;
bool foundNewSheet = false;
Worksheet worksheet = null;
public Form1()
{
InitializeComponent();
}
private void start_Button_Click(object sender, EventArgs e)
{
string inFile = myPath+ inFileName;
int nSheets = 0;
string sheetNames = "";
// Open Excel workbook to read
Microsoft.Office.Interop.Excel.Application xl = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = xl.Workbooks.Open(inFile);
// Count worksheets in opened Excel file
nSheets = workbook.Worksheets.Count;
nSheets_TextBox.Text = nSheets.ToString();
nInSheets = 0;
foreach (Worksheet worksheet in workbook.Worksheets)
++nInSheets;
//foreach (Worksheet worksheet in workbook.Worksheets)
for (int iSheet = nInSheets; iSheet >= 1; --iSheet)
{
worksheet = workbook.Worksheets[iSheet];
sheetNames += " " + worksheet.Name;
// The program is going to add a worksheet. If it already exists, delete it before adding it.
if (string.Equals(worksheet.Name, newSheetName))
{
workbook.Worksheets[iSheet].Delete();
}
}
// Add a new sheet and name it
if (foundRawSheet)
{
newWorksheet = workbook.Worksheets.Add();
newWorksheet.Name = newSheetName;
// THE NEXT LINE IS THE PROBLEM LINE
// "Written" WILL BE WRITTEN TO A1:C3 WHEN THE SHEET IS CREATED, BUT THIS LINE
// CAUSES THE ERROR IN SUBSEQUENT RUNS
// IF I COMMENT IT OUT, THE PROGRAM RUNS FINE, REPEATEDLY
newWorksheet.Range["A1", "C3"].Value2 = "Written";
workbook.Save();
workbook.Close();
xl.Quit();
}
}
}
Did you set xl.DisplayAlerts=false?
If not, deleting a worksheet with existing data will cause a confirm dialog to be displayed. .
If the Excel application is visible, the Worksheet.Delete will block until the dialog is acknowledged.
If the Excel application is not visible, your code execution will proceed (the dialog is effectively canceled --> delete not confirmed), but the worksheet will not be deleted.

Background Excel Thread Not Getting Killed C#

Need to work with Excel Interop. I can successfully open and read from an excel file but while closing it, the background process for that excel does not get killed. Tried using several solutions from previous SO links, but no luck! So my ask is, how to kill the background process???
Below is the UPDATED CODE that I am currently using:
Excel.Application application = new Excel.Application();
var workbooks = application.Workbooks;
Excel.Workbook workbook = workbooks.Open(path);
Excel.Worksheet worksheet = workbook.ActiveSheet;
Excel.Range range = worksheet.UsedRange;
var rows = range.Rows;
// Some business logic
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
// Some business logic
//close the excel
rows.Clear();
cell.Clear();
range.Clear();
workbook.Close(false);
application.Quit();
while (Marshal.FinalReleaseComObject(rows) != 0) { }
while (Marshal.FinalReleaseComObject(cell) != 0) { }
while (Marshal.FinalReleaseComObject(range) != 0) { }
while (Marshal.FinalReleaseComObject(worksheet) != 0) { }
while (Marshal.FinalReleaseComObject(workbook) != 0) { }
while (Marshal.FinalReleaseComObject(workbooks) != 0) { }
while (Marshal.FinalReleaseComObject(application) != 0) { }
rows = null;
cell = null;
range = null;
worksheet = null;
workbook = null;
workbooks = null;
application = null;
GC.Collect();
GC.WaitForPendingFinalizers();
By following the above code, I get the below exception in my debugger:
Any help on this will be appreciated.
You are using range.Rows.Count, this might violate the "Never use 2 dots with com objects." rule. See here
You could try including this ;
var rows = range.Rows
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
rows.Clear(); //rows is itself a range object
The few times I've had to use Excel interop, I haven't had any issues when following these simple rules:
Always wrap any Excel interop in try-finally
blocks. In the finally block put all releasing logic.
Use Marshal.FinalReleaseComObject to release named COM
references as its essentially doing the ref count loop for you.
Eagerly release COM objects from deepest to
shallowest. In your case I'd start with range then worksheet
then workbook and so on.
Correctly release unreferenced COM objects (two dot rule) with GC.Collect() and GC.WaitForPendingFinalizers(). Do this before manually
releasing pending COM objects you hold a named reference to.

Fastest way to generate Excel from datagridview with formatting

I have a code to export data from datagridview to Excel sheet but the problem is it is very slow because it is inserting data and formatting each cell.
How can I improve performance of this operation?
Below is my code
public static void ExcelExport(DataGridView Dg, string TypePass)
{
Microsoft.Office.Interop.Excel.ApplicationClass ExcelApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
ExcelApp.Application.Workbooks.Add(Type.Missing);
Excel_12.ApplicationClass oExcel_12 = null; //Excel_12 Application
Excel_12.Workbook oBook = null; // Excel_12 Workbook
Excel_12.Sheets oSheetsColl = null; // Excel_12 Worksheets collection
Excel_12.Worksheet oSheet = null; // Excel_12 Worksheet
Excel_12.Range oRange = null; // Cell or Range in worksheet
Object oMissing = System.Reflection.Missing.Value;
oExcel_12 = new Excel_12.ApplicationClass();
oExcel_12.UserControl = true;
oBook = oExcel_12.Workbooks.Add(oMissing);
oSheetsColl = oExcel_12.Worksheets;
oSheet = (Excel_12.Worksheet)oSheetsColl.get_Item("Sheet1");
oRange = (Excel_12.Range)oSheet.Cells[1, 1];
oRange.Value2 = "";
oRange.Font.Name = "Tahoma";
oRange.Font.Size = 12;
(oRange).Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.White);
(oRange).Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Gray);
if (TypePass.Trim().Length > 0)
{
oRange = (Excel_12.Range)oSheet.Cells[2, 1];
oRange.Value2 = TypePass;
oRange.Font.Name = "Tahoma";
oRange.Font.Size = 10;
}
int c = 0;
if (Dg.ColumnHeadersVisible == true)
{
for (int j = 0; j < Dg.Columns.Count; j++)
{
if (Dg.Columns[j].Visible == true)
{
oRange = (Excel_12.Range)oSheet.Cells[4, c + 1];
oRange.Value2 = Dg.Columns[j].HeaderText + " ";
oRange.Font.Bold = true;
oRange.Font.Name = "Tahoma";
oRange.Font.Size = 9;
(oRange).Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.White);
(oRange).Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Teal);
oExcel_12.Columns.AutoFit();
c++;
}
}
}
c = 0;
for (int i = 0; i < Dg.Rows.Count; i++)
{
for (int j = 0; j < Dg.Columns.Count; j++)
{
if (Dg.Columns[j].Visible == true)
{
oRange = (Excel_12.Range)oSheet.Cells[i + 5, c + 1];
if (Dg[j, i].Value == null)
{
oRange.Value2 = " ";
}
else
{
oRange.Value2 = Dg[j, i].Value.ToString().Replace('\n', ' ') + " ";
}
oRange.Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
oRange.Font.Name = "Tahoma";
oRange.Font.Size = 8;
oExcel_12.Columns.AutoFit();
// oRange.NumberFormat = "dd/MM/yyyy";
c++;
}
}
c = 0;
}
oExcel_12.Visible = true;
oBook = null;
oExcel_12 = null;
GC.Collect();
}
You can use Open XML SDK if you like.
I have used Open XML for export data to Excel spreadsheet (.XLSX format) and i can assure that performances are great.
I can generate 50,000 cell spreadsheet within 2, 3 seconds
1 Million cell spreadsheet within 60 seconds [That's 10,000 Row
100 Column spreadsheet]
What you need to know :
Lean how spreadsheet is structured
Follow given guides here and here
Learn about Styling [kind of PRO level which enables many possibilities]
Work with Open XML Productivity tool ; Will ease your learning curve guide
Advantage :
you can create well formatted Excel sheets without having Office
package installed.
Also you can expand spreadsheet generating even to server side if
you like.
At first you will feel it's hard compared to InterOp , but once you have properly implemented you will be able to use same Excel spreadsheet function for ANY project.!
If you decide to stick in Microsoft.Office.Interop.Excel, you can utilize the code by setting the format and data in range properly.
Set header styles for 1 row
Set content styles from columns * row
Build array from the DataGridView cell values then write it in range is a very quick way: Write Array to Excel Range
Btw, GC.Collect cannot serve the purpose for close the COM object, please reference to Proper disposal of COM interop objects in C# particularly MS Office applications
MS Office Interop is slow and even Microsoft does not recommend Interop usage on server side. For more details see what Microsoft stated on why not to use OLE Automation.
Microsoft Excel released XLSX file format with Office 2007 and recommends the usage of OpenXML SDK instead of Interop.
If you must save Excel files in XLS file format, you can use an Excel library like EasyXLS.
See the following code sample as alternative of exporting DataGridView to Excel:
// Create a DataSet and add the DataTable of DataGridView
DataSet dataSet = new DataSet();
dataSet.Tables.Add((DataTable)dataGridView);//or ((DataTable)dataGridView.DataSource).Copy() to create a copy
// Export Excel file
ExcelDocument workbook = new ExcelDocument();
workbook.easy_WriteXLSFile_FromDataSet(filePath, dataSet,
new EasyXLS.ExcelAutoFormat(EasyXLS.Constants.Styles.AUTOFORMAT_EASYXLS1),
"Sheet1");
For exporting the formatting that you need you can create your own ExcelAutoFormat. Check this code sample on how to export datagridview to Excel in C# with formatting.

Error Exporting to Excel C#

I'm getting an error when I'm exporting Excel to C#, I can't find where my code is wrong and the solution for my problem
Error :
An unhandled exception of type
'System.Runtime.InteropServices.COMException' occurred in GestãoSI.exe
Additional information: Índice inválido. (Excepção de HRESULT:
0x8002000B (DISP_E_BADINDEX))
The error appear when the code is running
// Add a workbook.
oBook = oExcel_12.Workbooks.Add(oMissing);
// Get worksheets collection
oSheetsColl = oExcel_12.Worksheets;
// Get Worksheet "Sheet1"
oSheet = (Excel_12.Worksheet)oSheetsColl.get_Item("Sheet1");
Here is all my code
public static void ExportDataGridViewTo_Excel12(DataGridView itemDataGridView)
{
Excel_12.Application oExcel_12 = null; //Excel_12 Application
Excel_12.Workbook oBook = null; // Excel_12 Workbook
Excel_12.Sheets oSheetsColl = null; // Excel_12 Worksheets collection
Excel_12.Worksheet oSheet = null; // Excel_12 Worksheet
Excel_12.Range oRange = null; // Cell or Range in worksheet
Object oMissing = System.Reflection.Missing.Value;
// Create an instance of Excel_12.
oExcel_12 = new Excel_12.Application();
// Make Excel_12 visible to the user.
oExcel_12.Visible = true;
// Set the UserControl property so Excel_12 won't shut down.
oExcel_12.UserControl = true;
// System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo("en-US");
// Add a workbook.
oBook = oExcel_12.Workbooks.Add(oMissing);
// Get worksheets collection
oSheetsColl = oExcel_12.Worksheets;
// Get Worksheet "Sheet1"
oSheet = (Excel_12.Worksheet)oSheetsColl.get_Item("Sheet1");
// Export titles
for (int j = 0; j < itemDataGridView.Columns.Count; j++)
{
oRange = (Excel_12.Range)oSheet.Cells[1, j + 1];
oRange.Value2 = itemDataGridView.Columns[j].HeaderText;
}
// Export data
for (int i = 0; i < itemDataGridView.Rows.Count - 1; i++)
{
for (int j = 0; j < itemDataGridView.Columns.Count; j++)
{
oRange = (Excel_12.Range)oSheet.Cells[i + 2, j + 1];
oRange.Value2 = itemDataGridView[j, i].Value;
}
}
// Release the variables.
//oBook.Close(false, oMissing, oMissing);
oBook = null;
//oExcel_12.Quit();
oExcel_12 = null;
// Collect garbage.
GC.Collect();
}
this work for me..
oSheet = oBook.Worksheets.get_Item(index);
Since you got a non-english exception text from Excel I assume there is no sheet that is named "Sheet1", instead it has the localized name. You have to either use the loclized name or, which would be way better, just use the sheet index (should start with 1) instead of the sheet name.

Categories

Resources