I am coding in C# .NET Core 2.2. I am trying to replace Excel Interop with EPPlusCore in my code for reliability and for portablilty. I have a series of invoices I am opening with EPPlus, but some of them throw a "Null Reference" exception when accessing the Workbook property of the Excel package.
This only happens when running the code without debugging it. When debugging, if I hover over the ExcelPackage item, it refreshes the reference to the Workbook and I am able to run the rest of the code.
public object[,] GetExcelDataEpplus(string filePath, int index,
bool sheetByName = false, string name = null, string password = null)
{
var remoteFileInfo = new FileInfo(filePath);
if (!remoteFileInfo.Exists)
{
throw new Exception("File does not exist: " + filePath);
}
var currentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "temp-excel");
if (!Directory.Exists(currentPath))
{
Directory.CreateDirectory(currentPath);
}
var localFileInfo = new FileInfo(Path.Combine(currentPath, remoteFileInfo.Name));
if (!localFileInfo.Exists)
{
File.Copy(filePath, localFileInfo.FullName);
}
object[,] values = null;
try
{
if (!File.Exists(localFileInfo.FullName))
{
_logger.LogInformation(DbLog, "Cannot find file : " + localFileInfo.FullName);
return null;
}
else
{
_logger.LogInformation(DbLog, "Found file : " + localFileInfo.FullName);
}
_logger.LogInformation(DbLog, "Initializing EPPlus...");
using (var package = string.IsNullOrEmpty(password)
? new ExcelPackage(localFileInfo)
: new ExcelPackage(localFileInfo, password))
{
_logger.LogInformation(DbLog, "Opening Workbook...");
//todo Error Thrown Here
try
{
package.Workbook.FormulaParserManager.LoadFunctionModule(new ImporterFunctionModule());
}
catch (Exception e)
{
_logger.LogWarning(DbLog, e, $"Could not load workbook : Loading file again...");
try
{
System.Threading.Thread.Sleep(1000);
package.Workbook.FormulaParserManager.LoadFunctionModule(new ImporterFunctionModule());
}
catch (Exception ex)
{
_logger.LogError(DbLog, ex, "Could not load workbook");
throw;
}
}
var workbook = package.Workbook;
_logger.LogInformation(DbLog, $"Calculating formulas...");
workbook.Calculate();
_logger.LogInformation(DbLog, "Finding Worksheet...");
var worksheet = sheetByName ? workbook.Worksheets.FirstOrDefault(x => x.Name == name) : workbook.Worksheets[index];
if (worksheet == null)
{
throw new Exception($"Could not find worksheet : {name}");
}
_logger.LogInformation(DbLog, $"Reading from worksheet : {worksheet.Name}...");
var start = worksheet.Dimension.Start;
var end = worksheet.Dimension.End;
values = worksheet.Cells[start.Row, start.Column, end.Row, end.Column].ToMultiDimensionalArray();
}
}
catch (Exception e)
{
_logger.LogError(DbLog, e, $"GetExcelInvoiceDataEpplus from {filePath} ({localFileInfo.FullName})"); //todo propogate error
}
finally
{
File.Delete(localFileInfo.FullName);
}
var rowCount = values?.GetLength(0) ?? 0;
_logger.LogInformation(DbLog, $"EPPLus found {rowCount} rows in the spreadsheet");
return values;
}
On most files, this works correctly, and I get a multidimensional array of the values from the specified worksheet tab. However, on some files, it does not work and I am at a loss as to why.
The closest similar problem I've been able to find is this: https://github.com/JanKallman/EPPlus/issues/416
But if this is accurate, how would I know what worksheet names have bad references without accessing the workbook first?
I found the solution. The excel files that were having problems were ones that were larger than average.
The solution was just to wait longer for them.
try
{
package.Workbook.FormulaParserManager.LoadFunctionModule(new ImporterFunctionModule());
}
catch (Exception e)
{
for (var i=1; i < 6; i++)
{
_logger.LogWarning(DbLog, e, $"Could not load workbook : Loading file again (Attempt #{i})...");
try
{
System.Threading.Thread.Sleep(2000);
package.Workbook.FormulaParserManager.LoadFunctionModule(new ImporterFunctionModule());
break;
}
catch (Exception ex)
{
if (i < 5) continue;
_logger.LogError(DbLog, ex, "Could not load workbook after 5 attempts");
throw;
}
}
}
Related
I am getting an error even when I dont update any anything.
when I run the code with epplus excel package anyone have a solution , i am using an xlsm file.
public void UpdateTheIntroExcelFile(IList<GeneralViewModel> Intro_Table, string Path)
{
try
{
if (Path != null)
{
ExcelPackage.LicenseContext = LicenseContext.Commercial;
var file = new FileInfo(fileName: Path);
using var ExPackage = new ExcelPackage(file);
var workSheet = ExPackage.Workbook.Worksheets[PositionID:0];
//workSheet.Cells[3, 30].Value = "Success";
ExPackage.Save();
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
}
I have an excel file that has around 40 to 50 sheets of size 8mb and will be increasing may be 15 to 20mb and many are inter-linked with multiple formulas.
So basically if one cell values changes then it will affect multiple sheets because of function and vlookup's etc.
Now I am trying to manipulate the excel sheet dynamically using c#. But I see that the are not calculating instantly.
I tried using ExcelLibrary's Calculate function to do this.
Problems with ExcelLibrary are:
a. It is very very slow with Calculate, ExcelPackage(Open) and Save functions**(Makes it unusable)**.
b. Need to know all the dependent cell to trigger calculate on them(not preferring workbook or worksheet calculate(the debugger never comes to next line)) which is not efficient way to manage code in feature.
I believe the problem is because it works with ooxml and actually not directly with excel(not sure we can work with excel directly so that it will be like inserting data manually using code).
Note: Trying to test with Excel interop(hoping it will do the job because it opens excel file in background while manipulating.)
private void TestExcelWithSimultaneousReadOfTwoSheetsFromFile(string fileName)
{
try
{
int count = 0;
int rowIndex = 1702;
for (int rowCount = 0; rowCount < 3; rowCount++)
{
count = ReadCountFromProdReport(fileName);
WriteRowToProd(fileName, rowIndex + rowCount);
count = ReadCountFromProdReport(fileName);
}
}
catch (Exception ex)
{
throw ex;
}
}
private void WriteRowToProd(string fileName, int rowIndex)
{
try
{
string logDirectory = System.Configuration.ConfigurationManager.AppSettings["LogDirectory"].ToString().Trim();
string filePath = logDirectory + fileName + ConfigurationManager.AppSettings["ExcelSaveFileExtension"].ToString().Trim();
using (ExcelPackage excelPackage = new ExcelPackage(new FileInfo(filePath)))
{
foreach (ExcelWorksheet workSheet in excelPackage.Workbook.Worksheets)
{
if (workSheet.Name.Trim() == "Sample")
{
workSheet.Cells["C" + rowIndex].Value = "15.05.2017";
workSheet.Cells["D" + rowIndex].Value = 21701503;
workSheet.Cells["E" + rowIndex].Value = "21701503109W";
workSheet.Cells["F" + rowIndex].Value = 304;
workSheet.Cells["G" + rowIndex].Value = 200;
workSheet.Cells["H" + rowIndex].Value = 1520;
workSheet.Cells["I" + rowIndex].Value = 11350;
workSheet.Cells["J" + rowIndex].Formula = "=7.85*G1701*H1701*I1701/1000000000";
workSheet.Cells["K" + rowIndex].Value = 27.080;
excelPackage.Save();
break;
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
private int ReadCountFromProdReport(string fileName)
{
try
{
string logDirectory = System.Configuration.ConfigurationManager.AppSettings["LogDirectory"].ToString().Trim();
string filePath = logDirectory + fileName + ConfigurationManager.AppSettings["ExcelSaveFileExtension"].ToString().Trim();
using (ExcelPackage excelPackage = new ExcelPackage(new FileInfo(filePath)))
{
object count = null;
foreach (ExcelWorksheet workSheet in excelPackage.Workbook.Worksheets)
{
if (workSheet.Name.Trim() == "Report")
{
count = workSheet.Cells["E23"].Value;
break;
}
}
return Convert.ToInt32(count);
}
}
catch (Exception ex)
{
throw ex;
}
}
As part of a file storage migration project, I am trying to change some excel links in some excel workbooks to reflect the new file storage location.
I am using Winforms and C# in VS2017 RC to develop the solution that I intend to deploy.
In my solution; I am calling the ChangeLink method on the Excel Workbook object and passing in the old link, the new link and the Excel Link Type.
public string ProcessFile(string fileName)
{
// Private member variable and placeholder declarations
missingValue = Type.Missing;
string oldLink;
string newLink;
int splitLocation;
string stringToFind = "\\Finance";
//Open the specified Excel Workbook
Excel.Workbook excelWorkbook;
StringBuilder resultsOut = new StringBuilder();
if (MsOfficeHelper.IsPasswordProtected(fileName))
{
resultsOut = resultsOut.AppendLine("Password Protected - " + fileName);
}
else
{
// Open
excelWorkbook = excelApp.Workbooks.Open(Filename: fileName, UpdateLinks: false);
Array olinks = excelWorkbook.LinkSources(Excel.XlLink.xlExcelLinks) as Array;
if (olinks != null)
{
if (olinks.Length > 0)
{
resultsOut = resultsOut.AppendLine("Contains Links - " + fileName);
foreach (var olink in olinks)
{
oldLink = olink.ToString();
splitLocation = oldLink.IndexOf(stringToFind, 0);
newLink = "C:\\SteveTest\\" + oldLink.Substring(splitLocation + 1);
resultsOut = resultsOut.AppendLine(oldLink);
resultsOut = resultsOut.AppendLine(newLink);
try
{
excelWorkbook.ChangeLink(Name: oldLink, NewName: newLink, Type: Excel.XlLinkType.xlLinkTypeExcelLinks);
}
catch (Exception whoopsy)
{
MessageBox.Show(whoopsy.Message);
//throw;
}
}
}
}
excelWorkbook.Close(SaveChanges: false);
}
return resultsOut.ToString();
}
However, when I execute the ChangeLink method I get the following exception
Does anyone have any idea what is causing the exception?
Your considered responses will be greatly welcome.
I've just signed up to the website so I have probably put this wrong. Anyways, I am trying to use the try and catch in C# to catch my file if it not found. Here is my code for it at the moment. To repeat myself, I want the program to read the file in, as it does- but then if the file is not found, I would like it to give an error saying "cannot find the file" or something along those lines, not simply just crash. (I've just started learning C#)
Thanks for the help!
string file = #"C:\Users\Henry\Desktop\Assessments\Programming and data structures\grades_multiple.txt";
try
{
file = #"C:\Users\Henry\Desktop\Assessments\Programming and data structures\grades_multiple.txt";
}
catch
{
Console.WriteLine("Could not find the file - grades_multiple.txt");
}
//ARRAY for string lines
string[] Line = new string[6];
Line[0] = File.ReadLines(file).Skip(1).Take(1).First();
You should read the file inside the try catch and catch FileNotFoundException, like this:
var file = #"C:\Users\Henry\Desktop\Assessments\Programming and data structures\grades_multiple.txt";
string[] lines;
try
{
lines = File.ReadAllLines(file);
}
catch (FileNotFoundException exnotfound)
{
// file not found exception
}
catch (Exception ex)
{
// handle other exceptions
}
You need to put the code that is error prone inside of try block.
try
{
Line[0] = File.ReadLines(file).Skip(1).Take(1).First();
}
catch(Exception ex)
{
Console.WriteLine("Could not find the file - grades_multiple.txt");
}
BTW, you can handle this situation by checking if file exists first using File.Exists method.There is no need to catch exception for that.
Try catch just doesn't work like this.
You are trying to catch a line of code that changes a string and doesn't change a file, so the file doesn't need to exist, so it will never throw an exception (in your case), and it will never catch it.
You should surround code that can go wrong: File.ReadLines
Your code will become like this:
string file = #"C:\Users\Henry\Desktop\Assessments\Programming and data structures\grades_multiple.txt";
//can go wrong
try
{
//ARRAY for string lines
string[] Line = new string[6];
Line[0] = File.ReadLines(file).Skip(1).Take(1).First();
}
//File.ReadLines is null
catch
{
Console.WriteLine("Could not find the file - grades_multiple.txt");
}
I also think it is even better practice to check it with an if statement, instead of catching it when it goes wrong:
//if file exists
if(File.Exists(file))
{
//ARRAY for string lines
string[] Line = new string[6];
Line[0] = File.ReadLines(file).Skip(1).Take(1).First();
}
//File does not exist
else
{
Console.WriteLine("Could not find the file - grades_multiple.txt");
}
When ever possible you should try to avoid throwing exceptions just to display a message to the user or for conditions that can be easily tested. This is primarily a performance consideration as throwing an exception is expensive. Additionally performance can be impacted by loading the entire file into memory unless you know that the file is relatively small..
Here is a quick and raw example on how you may test for the condition and handle it.
void Main()
{
var filePath ="C:\\TEST.DAT";
if(!File.Exists(filePath)){ DisplayFileNotFoundError(filePath); }
try
{
var lines = GetFileLines(filePath);
if(lines == null) { DisplayFileNotFoundError(filePath);}
// do work with lines;
}
catch (Exception ex)
{
DisplayFileReadException(ex);
}
}
void DisplayErrorMessageToUser(string filePath)
{
Console.WriteLine("The file does not exist");
}
void DisplayFileReadException(Exception ex){
Console.WriteLine(ex.Message);
}
string[] GetFileLines(string filePath){
if(!File.Exists(filePath)){ return null; }
string[] lines;
try
{
lines = File.ReadLines(filePath);
return lines;
}
catch (FileNotFoundException fnf){
Trace.WriteLine(fnf.Message);
return null;
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
throw ex;
}
}
Performance Test, FileNotFoundException vs File.Exists
void Main()
{
int max = 100000;
long[] feSampling = new long[max];
long[] exSampling = new long[max];
String pathRoot ="C:\\MISSINGFILE.TXT";
String path = null;
Stopwatch sw = new Stopwatch();
for (int i = 0; i < max; i++)
{
path = pathRoot + i.ToString();
sw.Start();
File.Exists(pathRoot);
sw.Stop();
feSampling[i] = sw.ElapsedTicks;
sw.Reset();
}
StreamReader sr = null;
sw.Reset();
for (int i = 0; i < max; i++)
{
path = pathRoot + i.ToString();
try
{
sw.Start();
sr = File.OpenText(path);
}
catch (FileNotFoundException)
{
sw.Stop();
exSampling[i] = sw.ElapsedTicks;
sw.Reset();
if(sr != null) { sr.Dispose();}
}
}
Console.WriteLine("Total Samplings Per Case: {0}", max);
Console.WriteLine("File.Exists (Ticsk) - Min: {0}, Max: {1}, Mean: {2}", feSampling.Min(), feSampling.Max(), feSampling.Average ());
Console.WriteLine("FileNotFoundException (Ticks) - Min: {0}, Max: {1}, Mean: {2}", exSampling.Min(), exSampling.Max(), exSampling.Average ());
}
I am having some problems with a piece of code, I am trying to import data from a source (at this time an access database) into a custom form but i keep getting the above error.
When i use a VBscript inside the source database all contacts import correctly.
When i repair the PST it still gives this error.
When i add a delay of 450 ms. the error also occurs but later on in the process.
Having Outlook opened or closed does not matter.
I am using the following method
string[] arrFolders = strFolders.Split('\\');
Outlook.Application app = null;
Outlook.MAPIFolder folder = null;
try {
app = new Outlook.Application();
folder = app.GetNamespace("MAPI").Folders[arrFolders[0]];
} catch (Exception ex) {
writeLogLine("Error creating Outlook instance: " + ex.Message);
MessageBox.Show("Error creating Outlook instance\r\n" + ex.Message);
intErrorCount++;
blnHasErrors = true;
blnAbort = true;
}
try {
for (int i = 1; i < arrFolders.Length; i++) {
folder = folder.Folders[arrFolders[i]];
}
} catch (Exception ex) {
writeLogLine("Error navigating to DRM folder: " + ex.Message);
MessageBox.Show("Error navigating to DRM folder\r\n" + ex.Message);
intErrorCount++;
blnHasErrors = true;
blnAbort = true;
}
setProgressbarMaximum(dtResults.Rows.Count);
setProgressbarMode(ProgressBarStyle.Continuous);
//int intRowCount = 0;
foreach (DataRow drItem in dtResults.Rows) {
if (strDRMType == "Contact") {
try {
Outlook.ContactItem x = (Outlook.ContactItem)folder.Items.Add("IPM.Contact." + strFormName);
for (int i = 0; i < arrMappings.GetLength(0); i++) {
if (arrMappings[i, 1] != null && drItem[arrMappings[i, 0]].ToString() != "") {
x.UserProperties[arrMappings[i, 1]].Value = drItem[arrMappings[i, 0]].ToString();
}
}
x.Save();
} catch (Exception ex) {
writeLogLine("Error importing contact: " + ex.Message);
intErrorCount++;
blnHasErrors = true;
}
}
as i said, when i loop the code it will throw exceptions after 100 to 200 contacts, when i add a delay it will get to contact 400/500 before failing.
This code is supposed to be for a generic import tool for this specific form so there is no need for hardcoding the source column names to the form fields in the import code.
Any help is appreciated.
I'm assuming this is not an Outlook add-in, since you say it doesn't matter if OL is open or closed, right?
One thing you may want to do is ensure you are releasing the COM objects once you are done with them, using System.Runtime.InteropServices.Marshal.ReleaseComObject(...). Also, when you use dot notation like "namespace.Folders[..].Name" you are actually leaking a reference to both the Folders collection object and a Folder object.
When you do folders.Items.Add(...) inside a loop, that leaks a lot of objects.
So, clean up your COM references first, and see how that affects your situation.
Here's how I typically use COM objects:
MyComLib.Foo foo = null;
try
{
foo = new MyComLib.Foo();
foo.DoSomething();
} catch(COMException exc)
{
// handle error, or rethrow
}
finally
{
if(foo != null)
Marshal.ReleaseComObject(foo);
}