Background Excel Thread Not Getting Killed C# - 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.

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.

passing a workbook Name into a For Loop C#

So I am doing a few For loops that iterate through different variable changes imported from a template, a SQL script run, data is dumped, loop to next SQL script, etc, and this is done multiple times using the same template file (which at the end of the query dumps is saved as a new file. With that as background:
I need to be able to open the template the first time through the loop, then keep it open through each query dump until done. I don't want to keep reopening the file as its just too big and cumbersome I have this so far:
public void ExportToExcel(DataSet dataSet, string templatePath)
{
Excel.Application excelApp = new Excel.Application();
excelApp.Visible = true;
FileInfo excelFileInfo = new FileInfo(templatePath);
Boolean fileOpenTest = IsFileLocked(excelFileInfo);
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
for (int i = 0; i < lstQueryDumpSheet.Items.Count; i++)
{
string tabName = lstQueryDumpSheet.Items[i].ToString();
Excel.Worksheet templateSheet = templateBook.Sheets[tabName];
// Copy DataTable
foreach (System.Data.DataTable dt in dataSet.Tables)
{ ... rest of loops...
My Problem is that the code line "Excel.Worksheet templateSheet = templateBook.Sheets[tabName];" tells me that "templateBook" is not assigned, but I am assigning it outside the IF statement so it should pass....right?
This is a problem of scope. Your code:
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
declares the templateBook variable inside the blocks and thus its scope is limited to within that block. To have the variable persist outside of those blocks you need to declare it outside like this:
Excel.Workbook templateBook;
if (!fileOpenTest)
{
templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
templateBook = excelApp.Workbooks[templatePath];
}

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.

understanding COM exception

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();

Categories

Resources