Excel process stays alive even after calling Quit - excel

I am creating Excel Sheet using Devexpress Exporter and then saving the file at a particular location.
After the creation of file, I have to open it, to add dropdownlist of items and then save it again in same location.
After all the operations, the file has to be emailed automatically to the email address from database.
Now if I have 1000 email addresses, and to automate this process, it is creating more than 10 instances of Excel.
How can I stop creation of those instance and how can I use excel operations without using more memory.
Code is as below:
protected string CreateExcelFile(string FilterName)
{
Random ranNumber = new Random();
int number = ranNumber.Next(0, 10000000);
string FileName = "TestDoc"+DateTime.Now.Year.ToString()+number.ToString()+DateTime.Now.Second.ToString()+".xls";
string path = #"c:\TestDocuments\"+FileName;
Directory.CreateDirectory(Path.GetDirectoryName(path));
FileStream fs = new FileStream(path, FileMode.OpenOrCreate);
XlsExportOptions options = new XlsExportOptions();
options.ExportHyperlinks = false;
ASPxExporter.WriteXls(fs, options);
fs.Close();
AddDropDownToExcel(path);
return path;
}
//Adding The Dropdownlist Of Items TO Generated Excel Sheet
protected void AddDropDownToExcel(string path)
{
Microsoft.Office.Interop.Excel.Application application = new Microsoft.Office.Interop.Excel.Application();
string fileName = path.Replace("\\", "\\\\");
string RowCount = "F" + (testgrid.VisibleRowCount + 1).ToString();
// Open Excel and get first worksheet.
var workbook = application.Workbooks.Open(fileName);
var worksheet = workbook.Worksheets[1] as Microsoft.Office.Interop.Excel.Worksheet;
// Set range for dropdownlist
var rangeNewStatus = worksheet.get_Range("F2", RowCount);
rangeNewStatus.ColumnWidth = 20;
rangeNewStatus.Validation.Add(Microsoft.Office.Interop.Excel.XlDVType.xlValidateList, Microsoft.Office.Interop.Excel.XlDVAlertStyle.xlValidAlertStop,
Microsoft.Office.Interop.Excel.XlFormatConditionOperator.xlBetween, "Item1,Item2,Item3,Item4");
// Save.
workbook.Save();
workbook.Close(Microsoft.Office.Interop.Excel.XlSaveAction.xlSaveChanges, Type.Missing, Type.Missing);
application.Quit();
}

First, I sincerely hope this isn't running on a server.
Then, if your problem is that too many instances of Excel are created, a thought is "don't create an instance every single time". Instead of starting Excel every time AddDropDownToExcel is called, can you reuse the same instance?
The problem you are having shows up regularly in Excel interop scenario; even though you are done and tell Excel to close, it "stays alive". It's usually caused by your app still holding a reference to a COM object that hasn't been disposed, preventing Excel from closing. This StackOverflow answer provides some pointers: https://stackoverflow.com/a/158752/114519
In general, to avoid that problem, you want to follow the "one-dot" rule. For instance, in your code:
var workbook = application.Workbooks.Open(fileName);
will be a problem, because an "anonymous" wrapper for Workbooks is created, and will likely not be disposed properly. The "one-dot" rule would say "don't use more than one dot when working with Excel interop", in this case:
var workbooks = application.Workbooks;
var workbook = workbooks.Open(fileName);
A totally different thought - instead of using Interop, can't you use OpenXML to generate your Excel file? I have never tried it to create drop downs, but if it supports it, it will be massively faster than Interop, and the type of problems you have won't happen.
Hope this helps.

As I know the grow of number of runnig excel.exe processes is 'normal' situation to excel :)
The dumbest advice is just kill sometimes it's processes. BUT, this way will be absolutely unhelpful if you use excel during your app is working because of you rather don't get which one excel.exe is yours.

Related

Error accessing Names in Excel using Interop

I am programming an application in C# (Visual Studio 2015) and I need to update an .xlsm file.
This file has many formulas, over 1200 names and vba code.
I am using the Interop library and I am able to update some cells and get the relative updated formulas but i have some problem with the Names defined in the Excel.
The program recognizes the names in the Names collection but doesnt let me access some of the names.
When I try to access the value of the cell using its name it produces an exception.
I dont understand why i can access some of the names and others no.
Besides, in the excel, I can see the Name in the combo but when I select it, the cursor doesn't position over the cell.
In my program I could avoid this problem accessing the cells using the reference instead of the Name, but the vba in the excel uses the names and if i open the file from my app it doesnt work.
I am using this code:
excelApplication = new Microsoft.Office.Interop.Excel.Application();
excelApplication.ScreenUpdating = true;
excelApplication.Visible = true;
excelApplication.DisplayAlerts = false;
excelWorkbook = excelApplication.Workbooks.Open(txtFicheroEntrada.Text);
wsDatos = excelWorkbook.Worksheets[1];
wsDatos.Select();
foreach(Microsoft.Office.Interop.Excel.Name v in excelWorkbook.Names)
{
string NombreVar = v.Name;
//here i found the name BobinadoAT correctly. It exists
if (NombreVar == "BobinadoAT" ){ Console.WriteLine(NombreVar); }
}
if (wsDatos.Range["BobinadoAT"] != null) //but here this produces an exception
{
string valorcelda = wsDatos.Range["BobinadoAT"].Value.ToString();
}
¿does anyone work with many excel Names?
¿Am I accessing the names incorrectly?

I want to add new sheet to an existing csv file, but I dont know how to go about it

I want to add new sheet to an existing csv file, but I dont know how to go about it. I already opened the .csv file and i can access each element. so i want to create a new sheet on the existing .csv file and populate the cells with the data from the previous sheet.
class Program
{
static void Main(string[] args)
{
var reader = new StreamReader(File.OpenRead(#"C:\Users\Desktop\test.csv"));
List<string> listA = new List<string>();
List<string> listB = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
//line = line.Skip(1);
var values = line.Split(',');
listA.Add(values[0]);
listB.Add(values[1]);
listA.ForEach(Console.WriteLine);
listB.ForEach(Console.WriteLine);
Console.ReadLine();
}
}
}
I'm going to post this as an answer, even though it's kind of a non-answer. CSV files are simple flat-text files that are comma delimited. There are no higher-level concepts to this file type such as sheets, or cells, workbooks, or formulas.
Since they are just simple text files that are specially formatted, there is no concept of sheets. Instead you can maybe create additional CSV files and name the files accordingly.
If you want to create Excel files, and have individual sheets you can use various libraries or the COM Interops to do this.
COM Interops are for direct connections to Excel natively. Here's a MSDN How-To for Excel. This allows you to create a special object that will allow you to use Excel's API even though it's not a managed API through the .NET Framework.
Here's an example on how to add a sheet in that situation:
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
if (xlApp == null)
{
Console.WriteLine("EXCEL could not be started. Check that your office installation and project references are correct.");
return;
}
xlApp.Visible = true;
Workbook wb = xlApp.Workbooks.Add(XlWBATemplate.xlWBATWorksheet); //adds worksheet to our workbook
Worksheet ws = (Worksheet)wb.Worksheets[1]; //access that worksheet linked into the workbook
if (ws == null)
{
Console.WriteLine("Worksheet could not be created. Check that your office installation and project references are correct.");
}
Another option is to use the Open XML SDK for Office, which can be used for the new Office formats (.xlsx for example). Personally, I've never used this library, but it's similar to Apache POI for the .NET Framework.

Determine first sheet name of an Excel file when reading xls as a database

I have a need to read in some client data from an Excel sheet. I found it very quick and easy to read the *.xls as data, this way:
string connString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\MyExcelFile.xls;Extended Properties=\"Excel 8.0;HDR=YES\"";
using (var conn = new System.Data.OleDb.OleDbConnection(connString)) {
conn.Open();
System.Data.OleDb.OleDbCommand cmd = new System.Data.OleDb.OleDbCommand("Select * From [SheetName$]", conn);
OleDbDataReader reader = cmd.ExecuteReader();
int firstNameOrdinal = reader.GetOrdinal("First Name");
int lastNameOrdinal = reader.GetOrdinal("Last Name");
while (reader.Read()) {
Console.WriteLine("First Name: {0}, Last Name: {1}",
reader.GetString(firstNameOrdinal),
reader.GetString(lastNameOrdinal));
}
}
The problem comes in at the 4th line: SELECT * FROM [SheetName$]. In this example, you need to replace "SheetName" with the name of the worksheet you are trying to read. Most of the time, the client names this the same, and all is fine.
But sometimes they don't, and that means some manual work on our part to fix it. There is always just one sheet (in this case anyway) and so I'd rather just tell the code to "use the first sheet, whatever it is named".
I haven't been able to find a way to accomplish this using this technique, so I'm reaching out to the community. Thanks in advance for any advice.
PS - We have to go through a length approval process for external files (e.g. FileHelpers) and I prefer using this process because it does not require a third-party library. I'd prefer to limit answers to this method if possible.
Thanks.
The answer here might suit your needs:
http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/711cf5f9-75fc-4a02-9a96-08aec48dad69/
It uses .GetOleDbSchemaTable() to get a list of sheet names in a workbook.

Can I import INTO excel from a data source without iteration?

Currently I have an application that takes information from a SQLite database and puts it to Excel. However, I'm having to take each DataRow, iterate through each item, and put each value into it's own cell and determine highlighting. What this is causing is 20 minutes to export a 9000 record file into Excel. I'm sure it can be done quicker than that. My thoughts are that I could use a data source to fill the Excel Range and then use the column headers and row numbers to format only those rows that need to be formatted. However, when I look online, no matter what I seem to type, it always shows examples of using Excel as a database, nothing about importing into excel. Unless I'm forgetting a key word or to. Now, this function has to be done in code as it's part of a bigger application. Otherwise I would just have Excel connect to the DB and pull the information itself. Unfortunately that's not the case. Any information that could assist me in quick loading an excel sheet would be appreciated. Thanks.Additional Information:Another reason why the pulling of the information from the DB has to be done in code is that not every computer this is loaded on will have Excel on it. The person using the application may simply be told to export the data and email it to their supervisor. The setup app includes the needed dlls for the application to make the proper format.Example Code (Current):
For Each strTemp In strColumns
excelRange = worksheet.Cells(1, nCounter)
excelRange.Select()
excelRange.Value2 = strTemp
excelRange.Interior.Color = System.Drawing.Color.Gray.ToArgb()
excelRange.BorderAround(Excel.XlLineStyle.xlContinuous, Excel.XlBorderWeight.xlThin, Excel.XlColorIndex.xlColorIndexAutomatic, Type.Missing)
nCounter += 1
Next
Now, this is only example code in terms of the iteration I'm doing. Where I'm really processing the information from the database I'm iterating through a dataTable's Rows, then iterating through the items in the dataRow and doing essentially the same as above; value by value, selecting the range and putting the value in the cell, formatting the cell if it's part of a report (not always gray), and moving onto the next set of data. What I'd like to do is put all of the data in the excel sheet (A2:??, not a row, but multiple rows) then iterate through the reports and format each row then. That way, the only time I iterate through all of the records is when every record is part of a report.
Ideal Code
excelRange = worksheet.Cells("A2", "P9000")
excelRange.DataSource = ds 'ds would be a queried dataSet, and I know there is no excelRange.DataSource.
'Iteration code to format cells
Update:
I know my examples were in VB, but it's because I was also trying to write a VB version of the application since my boss prefers VB. However, here's my final code using a Recordset. The ConvertToRecordset function was obtained from here.
private void CreatePartSheet(Excel.Worksheet excelWorksheet)
{
_dataFactory.RevertDatabase();
excelWorksheet.Name = "Part Sheet";
string[] strColumns = Constants.strExcelPartHeaders;
CreateSheetHeader(excelWorksheet, strColumns);
System.Drawing.Color clrPink = System.Drawing.Color.FromArgb(203, 192, 255);
System.Drawing.Color clrGreen = System.Drawing.Color.FromArgb(100, 225, 137);
string[] strValuesAndTitles = {/*...Column Names...*/};
List<string> lstColumns = strValuesAndTitles.ToList<string>();
System.Data.DataSet ds = _dataFactory.GetDataSet(Queries.strExport);
ADODB.Recordset rs = ConvertToRecordset(ds.Tables[0]);
excelRange = excelWorksheet.get_Range("A2", "ZZ" + rs.RecordCount.ToString());
excelRange.Cells.CopyFromRecordset(rs, rs.RecordCount, rs.Fields.Count);
int nFieldCount = rs.Fields.Count;
for (int nCounter = 0; nCounter < rs.RecordCount; nCounter++)
{
int nRowCounter = nCounter + 2;
List<ReportRecord> rrPartReports = _lstReports.FindAll(rr => rr.PartID == nCounter).ToList<ReportRecord>();
excelRange = (Excel.Range)excelWorksheet.get_Range("A" + nRowCounter.ToString(), "K" + nRowCounter.ToString());
excelRange.Select();
excelRange.NumberFormat = "#";
if (rrPartReports.Count > 0)
{
excelRange.Interior.Color = System.Drawing.Color.FromArgb(230, 216, 173).ToArgb(); //Light Blue
foreach (ReportRecord rr in rrPartReports)
{
if (lstColumns.Contains(rr.Title))
{
excelRange = (Excel.Range)excelWorksheet.Cells[nRowCounter, lstColumns.IndexOf(rr.Title) + 1];
excelRange.Interior.Color = rr.Description.ToUpper().Contains("TAG") ? clrGreen.ToArgb() : clrPink.ToArgb();
if (rr.Description.ToUpper().Contains("TAG"))
{
rs.Find("PART_ID=" + (nCounter + 1).ToString(), 0, ADODB.SearchDirectionEnum.adSearchForward, "");
excelRange.AddComment(Environment.UserName + ": " + _dataFactory.GetTaggedPartPrevValue(rs.Fields["POSITION"].Value.ToString(), rr.Title));
}
}
}
}
if (nRowCounter++ % 500 == 0)
{
progress.ProgressComplete = ((double)nRowCounter / (double)rs.RecordCount) * (double)100;
Notify();
}
}
rs.Close();
excelWorksheet.Columns.AutoFit();
progress.Message = "Done Exporting to Excel";
Notify();
_dataFactory.RestoreDatabase();
}
Can you use ODBC?
''http://www.ch-werner.de/sqliteodbc/
dbName = "c:\docs\test"
scn = "DRIVER=SQLite3 ODBC Driver;Database=" & dbName _
& ";LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"
Set cn = CreateObject("ADODB.Connection")
cn.Open scn
Set rs = CreateObject("ADODB.Recordset")
rs.Open "select * from test", cn
Worksheets("Sheet3").Cells(2, 1).CopyFromRecordset rs
BTW, Excel is quite happy with HTML and internal style sheets.
I have used the Excel XML file format in the past to write directly to an output file or stream. It may not be appropriate for your application, but writing XML is much faster and bypasses the overhead of interacting with the Excel Application. Check out this Introduction to Excel XML post.
Update:
There are also a number of libraries (free and commercial) which can make creating excel document easier for example excellibrary which doesn't support the new format yet. There are others mentioned in the answers to Create Excel (.XLS and .XLSX) file from C#
Excel has the facility to write all the data from a ADO or DAO recordset in a single operation using the CopyFromRecordset method.
Code snippet:
Sheets("Sheet1").Range("A1").CopyFromRecordset rst
I'd normally recommend using Excel to pull in the data from SQLite. Use Excel's "Other Data Sources". You could then choose your OLE DB provider, use a connection string, what-have-you.
It sounds, however, that the real value of your code is the formatting of the cells, rather than the transfer of data.
Perhaps refactor the process to:
have Excel import the data
use your code to open the Excel spreadsheet, and apply formatting
I'm not sure if that is an appropriate set of processes for you, but perhaps something to consider?
Try this out:
http://office.microsoft.com/en-au/excel-help/use-microsoft-query-to-retrieve-external-data-HA010099664.aspx
Perhaps post some code, and we might be able to track down any issues.
I'd consider this chain of events:
query the SQLite database for your dataset.
move the data out of ADO.NET objects, and into POCO objects. Stop using DataTables/Rows.
use For Each to insert into Excel.

How to get a value out of an Excel workbook stored in a SharePoint document library?

I have some data that's currently stored in an Excel workbook. It makes sense for the data to be in Excel (in that it's easy to manage, easy to extend, do calcs, etc.) but some of the data there is required by an automated process, so from that point of view it would be more convenient if it were in a database.
To give the information more visibility, workflow, etc. I'm thinking of moving it to SharePoint. Actually turning it into a SharePoint form would be tedious & time-consuming, and then the flexibility/convenience would be lost; instead, I'm thinking of simply storing the current Excel file within a SharePoint library.
My problem then would be: how can the automated process extract the values it needs from the Excel workbook that now lives within the SharePoint library? Is this something that Excel Services can be used for? Or is there another/better way? And even if it can be done, is it a sensible thing to do?
Having gone through something similar, I can tell you it actually isn't that bad getting values out of an Excel file in a document library. I ended up writing a custom workflow action (used within a SharePoint Designer workflow) that reads values out of the Excel file for processing. I ended up choosing NPOI to handle all of the Excel operations.
Using NPOI, you can do something like this:
// get the document in the document library
SPList myList = web.Lists[listGuid];
SPListItem myItem = myList.GetItemById(ListItem);
SPFile file = myItem.File;
using (Stream stream = file.OpenBinaryStream())
{
HSSFWorkbook workbook = new HSSFWorkbook(stream);
HSSFSheet sheet = workbook.GetSheet("Sheet1");
CellReference c = new CellReference("A1");
HSSFRow row = sheet.GetRow(c.Row);
HSSFCell cell = row.GetCell(c.Col);
string cellValue = cell.StringCellValue;
// etc...
}
You could easily put this in a console application as well.
Yes, I am trying to extract a range of cells on several sheets within a workbook. I was able to use some of the code below in a console application and view the data within the command window. Now I need to dump the data to a SQL Table and was looking for some examples on how to accomplish this and make sure I am going down the correct coding path.
Here is a snapshot of the code I am using.
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
using (SPSite site = new SPSite(SPContext.Current.Site.Url))
{
using (SPWeb web = site.RootWeb)
{
SPList docList = web.Lists[__ListId];
SPListItem docItem = docList.GetItemById(__ListItem);
SPFile docFile = docItem.File;
using (Stream stream = docFile.OpenBinaryStream())
{
HSSFWorkbook wb = new HSSFWorkbook(stream);
//loop through each sheet in file, ignoring the first sheet
for (int i = 1; i < 0; i++)
{
NPOI.SS.UserModel.Name name = wb.GetNameAt(i);
String sheet = wb.GetSheetName(i);
NPOI.SS.UserModel.Name nameRange = wb.CreateName();
nameRange.NameName = ("DispatchCells");
//start at a specific area on the sheet
nameRange.RefersToFormula = (sheet + "!$A$11:$AZ$100");
}
wb.Write(stream);
}
}
}
return ActivityExecutionStatus.Closed;
}

Resources