Writing to Excel: Cannot access closed stream with EPPLUS - c#

I've looked around, and for the most part I see examples for more complex problems than my own.
So, I've been suggested to use EPPLUS as opposed to EXCEL INTEROP because of the performance improvement. This is my first time using it, and the first time I've encountered memory streams, so I'm not exactly sure what's wrong here.
I'm trying to write to an Excel file and convert that excel file into a PDF. To do this, I installed through NUGET the following:
EPPLUS
EPPLUSExcel
This is my code:
if (DGVmain.RowCount > 0)
{
//Source
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Excel Files|*.xls;*.xlsx";
openFileDialog.ShowDialog();
lblSuccess.Text = openFileDialog.FileName;
lblPathings = Path.ChangeExtension(openFileDialog.FileName, null);
int count = DGVmain.RowCount;
int current = 0;
int ballast = 0;
For each row in a DataGridView, perform write to Excel, then convert to PDF.
foreach (DataGridViewRow row in DGVmain.Rows)
{
//Drag
if (lblSuccess.Text == null)
return;
string drags = Convert.ToString(row.Cells[0].Value);
string dragsy = Convert.ToString(row.Cells[1].Value);
Persona = drag;
generateID();
//Initialize the Excel File
try
{
Here is where I expect something to be wrong:
using (ExcelPackage p = new ExcelPackage())
{
using (FileStream stream = new FileStream(lblSuccess.Text, FileMode.Open))
{
ballast++;
lblItem.Text = "Item #" + ballast;
p.Load(stream);
ExcelWorkbook WB = p.Workbook;
if (WB != null)
{
if (WB.Worksheets.Count > 0)
{
ExcelWorksheet WS = WB.Worksheets.First();
WS.Cells[82, 12].Value = drag13;
WS.Cells[84, 12].Value = "";
WS.Cells[86, 12].Value = 0;
//========================== Form
WS.Cells[95, 5].Value = drag26;
WS.Cells[95, 15].Value = drag27;
WS.Cells[95, 24].Value = drag28;
WS.Cells[95, 33].Value = drag29;
//========================== Right-Seid
WS.Cells[14, 31].Value = drag27;
WS.Cells[17, 31].Value = drag27;
}
}
Byte[] bin = p.GetAsByteArray();
File.WriteAllBytes(lblPathings, bin);
}
p.Save();
}
}
catch (Exception ex)
{
MessageBox.Show("Write Excel: " + ex.Message);
}
Separate method to convert to PDF, utilizing EPPLUSEXCEL and SpireXLS.
finally
{
ConvertToPdf(lblSuccess.Text, finalformat);
}
}
}
The compiler is not throwing any errors except the one mentioned in the title.

You already saved the ExcelPackage here:
Byte[] bin = p.GetAsByteArray();
So when you later try and save it again here:
p.Save();
the ExcelPackage is already closed. I.e. remove the Save() call in your code and you're good.

Related

C# Error: Microsoft Excel cannot access the file '...'. There are several possible reasons

I've developed an ASP.Net MVC application, that is running on a IIS sever. I've wrote a code that reads a CSV and insert the rows of it in a database.
[HttpPost]
public ActionResult InsertPosition(int id, HttpPostedFileBase position)
{
var posicoesExistentes = db.tbPositions.Where(s => s.id_unique == id).AsEnumerable();
foreach (tbPosition posicao in posicoesExistentes)
{
db.tbPositions.Remove(posicao);
}
if (!Directory.Exists(Server.MapPath("~/App_Data/")))
{
System.IO.Directory.CreateDirectory(Server.MapPath("~/App_Data/"));
}
string excelPath = Server.MapPath("~/App_Data/" + position.FileName);
if (System.IO.File.Exists(excelPath))
{
System.IO.File.Delete(excelPath);
}
position.SaveAs(excelPath);
string tempPath = Server.MapPath("~/App_Data/" + "tmp_" + position.FileName);
System.IO.File.Copy(excelPath, tempPath, true);
Excel.Application application = new Excel.Application();
Excel.Workbook workbook = application.Workbooks.Open(tempPath, ReadOnly: true,Editable:false);
Excel.Worksheet worksheet = workbook.ActiveSheet;
Excel.Range range = worksheet.UsedRange;
application.Visible = true;
for (int row = 1; row < range.Rows.Count - 1; row++)
{
tbPosition p = new tbPosition();
p.position = (((Excel.Range)range.Cells[row, 1]).Text == "") ? null : Convert.ToInt32(((Excel.Range)range.Cells[row, 1]).Text);
p.left = ((Excel.Range)range.Cells[row, 2]).Text;
p.right = ((Excel.Range)range.Cells[row, 3]).Text;
p.paper = ((Excel.Range)range.Cells[row, 4]).Text;
p.denomination = ((Excel.Range)range.Cells[row, 5]).Text;
p.material = ((Excel.Range)range.Cells[row, 6]).Text;
p.norme = ((Excel.Range)range.Cells[row, 7]).Text;
p.finalized_measures = ((Excel.Range)range.Cells[row, 8]).Text;
p.observation = ((Excel.Range)range.Cells[row, 9]).Text;
p.id_unique = id;
db.tbPositions.Add(p);
db.SaveChanges();
}
workbook.Close(true, Type.Missing, Type.Missing);
application.Quit();
System.IO.File.Delete(tempPath);
return Json("Success", JsonRequestBehavior.AllowGet);
}
but in return I got the error ' Microsoft Excel cannot access the file '...'. There are several possible reasons' when I try to open the requested excel file.
I've already tried to open the file as readonly, I've already tried to give permissions to the specifieds folders, multiples ways of close the excel file, and create an copy file of the original and read him. But unsuccessful in each one of these solutions. What have I missed here?
Unsupported
The short answer is that trying to programatically manipulate an Excel document using the Automation API is not supported outside of a UI context. You will come across all sorts of frustrations (for example, the API is permitted to show dialogs - how are you going to click on "OK" if it's running on a web-server?).
Microsoft explicitly state this here
Microsoft does not recommend or support server-side Automation of Office.
So what do I use?
I would recommend using the OpenXML SDK - this is free, fully supported and much faster than the Automation API.
Aspose also has a set of products, but they are not free, and I've not used them.
But I HAVE to do it this way
However, if you absolutely have to use the COM API then the following might help you:
HERE BE DRAGONS
The big problem with automation in Excel is that you need to ensure you close every single reference whenever you use them (by calling ReleaseComObject on it).
For example, the following code will cause Excel to stay open:
var range;
range = excelApplication.Range("A1");
range = excelApplication.Range("A2");
System.Runtime.InteropServices.Marshal.ReleaseComObject(range)
range = Nothing
This is because there is still a reference left over from the call to get range "A1".
Therefore, I would recommend writing a wrapper around the Excel class so that any access to, e.g., a range frees any previous ranges accessed before accessing the new range.
For reference, here is the code I used to release COM objects in the class I wrote:
Private Sub ReleaseComObject(ByVal o As Object)
Try
If Not IsNothing(o) Then
While System.Runtime.InteropServices.Marshal.ReleaseComObject(o) > 0
'Wait for COM object to be released.'
End While
End If
o = Nothing
Catch exc As System.Runtime.InteropServices.COMException
LogError(exc) ' Suppress errors thrown here '
End Try
End Sub
Try this
protected void ImportCSV(object sender, EventArgs e)
{
importbtn();
}
public class Item
{
public Item(string line)
{
var split = line.Split(',');
string FIELD1 = split[0];
string FIELD2 = split[1];
string FIELD3 = split[2];
string mainconn = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
using (SqlConnection con = new SqlConnection(mainconn))
{
using (SqlCommand cmd = new SqlCommand("storedProcedureName", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#FIELD1", SqlDbType.VarChar).Value = FIELD1;
cmd.Parameters.AddWithValue("#FIELD2", SqlDbType.VarChar).Value = FIELD2;
cmd.Parameters.AddWithValue("#FIELD3", SqlDbType.VarChar).Value = FIELD3;
con.Open();
cmd.ExecuteNonQuery();
}
}
}
}
private void importbtn()
{
try
{
string csvPath = Server.MapPath("~/Files/") + Path.GetFileName(FileUpload1.PostedFile.FileName);
FileUpload1.SaveAs(csvPath);
var listOfObjects = File.ReadLines(csvPath).Select(line => new Item(line)).ToList();
DataTable dt = new DataTable();
dt.Columns.AddRange(new DataColumn[3] { new DataColumn("FIELD1", typeof(string)),
new DataColumn("FIELD2", typeof(string)),
new DataColumn("FIELD3",typeof(string)) });
string csvData = File.ReadAllText(csvPath);
foreach (string row in csvData.Split('\n'))
{
if (!string.IsNullOrEmpty(row))
{
dt.Rows.Add();
int i = 0;
//Execute a loop over the columns.
foreach (string cell in row.Split(','))
{
dt.Rows[dt.Rows.Count - 1][i] = cell;
i++;
}
}
}
GridView1.DataSource = dt;
GridView1.DataBind();
Label1.Text = "File Attached Successfully";
}
catch (Exception ex)
{
Message.Text = "Please Attach any File" /*+ ex.Message*/;
}
}

Loading tsv/csv file in excel sheet

This is my code.
public static string LoadPackage(DirectoryInfo outputDir, string name)
{
FileInfo newFile = new FileInfo(outputDir.FullName + #"\test.xlsx");
if (newFile.Exists)
{
newFile.Delete();
newFile = new FileInfo(outputDir.FullName + #"\test.xlsx");
}
var format = new ExcelTextFormat();
format.Delimiter = '\t';
format.SkipLinesBeginning = 1;
using (ExcelPackage package = new ExcelPackage())
{
LoadSheet(package, outputDir, name);
package.SaveAs(newFile);
}
return newFile.FullName;
}
And after that i call LoadSheet method in order to fill my excel file from tsv file.
public static void LoadSheet(ExcelPackage package, DirectoryInfo
outputDir, string name)
{
var ws = package.Workbook.Worksheets.Add("Content");
var format = new ExcelTextFormat();
format.Delimiter = '\t';
format.SkipLinesBeginning = 2;
format.SkipLinesEnd = 1;
var range = ws.Cells["A1"].LoadFromText(new
FileInfo(outputDir.FullName + "\\" + name), format,
TableStyles.Medium27, false);
}
And this is my code on button click event
if (BrowseFileUpload.HasFile)
{
var name = BrowseFileUpload.PostedFile.FileName;
InputTextBox.Text = name;
LoadData.LoadPackage(new
System.IO.DirectoryInfo("C:\\Users\\Nemanja\\Downloads"), name);
InfoLabel.Text = "Your data has been imported!!!";
InfoLabel.ForeColor = System.Drawing.Color.Blue;
InfoLabel.Font.Size = 20;
}
Everything is ok i create new excel file, sheet save it but it does not load data that i need it to load inside excel file. It's only empty file or i get a error the file is corrupted recover what you can.
Can someone figure out what can be a problem based on my explanation and this code. Thank you all good people.
I think that the problem may well be with the format of your source data. I've put together the following sample, based on your code, and it works fine.
var outFile = Path.ChangeExtension(filePath, ".xlsx");
using (var p = new ExcelPackage())
{
var fmt = new ExcelTextFormat();
fmt.Delimiter = '\t';
fmt.SkipLinesBeginning = 2;
fmt.SkipLinesEnd = 1;
fmt.EOL = ((char)10).ToString(); // THIS LINE FIXED THE PROBLEM (UNIX NEWLINE)
var ws = p.Workbook.Worksheets.Add("Imported Text");
ws.Cells[1, 1].LoadFromText(new FileInfo(filePath), fmt, TableStyles.Medium27, false);
p.SaveAs(new FileInfo(outFile));
}
Try running your data through this and see if you get the same issue or not.
UPDATED
The problem was a unix-style newline in the file - EPPlus expects a windows-style newline by default

Exception using C# to ChangeLink IN Excel

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.

EPPlus Dispose Doesn't Work

I want to create workbook and then write data using EPPlus. When I create new workbook, it can create successfully. But when I want to write some data to that worksheet, it failed and error says
The process cannot access the file 'filename' because it is being
used by another process.
I have disposed previous ExcelPackage but the error still show when I write data.
//Create new Workbook
private void PengisianBaruBW_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke(new MethodInvoker(delegate
{
SetPengisianBtn.Enabled = false;
}));
FileInfo filePath = new FileInfo("D:\\Data Pengisian SLA Surabaya\\" + day + "_" + date + ".xlsx");
if (File.Exists(filePath.ToString()))
{
File.Delete(filePath.ToString());
}
using (ExcelPackage pck = new ExcelPackage(filePath))
{
var schedule = pck.Workbook.Worksheets.Add("Schedule");
var cart = pck.Workbook.Worksheets.Add("Cartridge");
var unsche = pck.Workbook.Worksheets.Add("Unschedule");
var rekap = pck.Workbook.Worksheets.Add("Rekap");
//My Code here
pck.SaveAs(filePath);
pck.Dispose(); //I have disposed ExcelPakcage here
}
}
//Write Data to Excel File
private void PrintScheduleBtn_Click(object sender, EventArgs e)
{
if (StaffATB.Text != "" && HelperTeamATB.Text != "" && StaffBTB.Text != "" && HelperTeamBTB.Text != "" && StaffCTB.Text != "" && HelperTeamCTB.Text != "" && StaffDTB.Text != "" && HelperTeamDTB.Text != "")
{
DialogResult dialogResult = MessageBox.Show("Apakah Anda yakin ingin menyimpan jadwal pengisian ?", "", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
FileInfo file = new FileInfo("D:\\Data Pengisian SLA Surabaya\\" + day + "_" + date + ".xlsx");
using (ExcelPackage pck = new ExcelPackage(file)) //error here
{
var rekap = pck.Workbook.Worksheets["Rekap"];
var data = pck.Workbook.Worksheets["Data"];
//my code to write data here
pck.SaveAs(file);
pck.Dispose();
}
}
}
else
{
MessageBox.Show("Silakan isi PIC terlebih dahulu !");
}
}
I have added this code to check whether my excel file is active or not. But the error still exsit. I set breakpoint and I see that stream value is null that indicate that my excel file is close. But why the error still exists ? Can anyone help me ?
string file = "D:\\Data Pengisian SLA Surabaya\\" + day + "_" + date + ".xlsx";
var path = Path.Combine(Path.GetTempPath(), "D:\\Data Pengisian SLA Surabaya\\" + day + "_" + date + ".xlsx");
var tempfile = new FileInfo(path);
FileStream stream = null;
try
{
stream = tempfile.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Close();
}
I simplified your snippet for testing. It all worked as it should. Are you sure there is no other cause of the file access problem, like a virus scanner, backup program etc. since you also have another question with the same basic problem.
Take a look at the snippet below, try it and see if this one works. If not the problem is not in the code.
FileInfo filePath = new FileInfo("ExcelDemo.xlsx");
if (File.Exists(filePath.ToString()))
{
File.Delete(filePath.ToString());
}
using (ExcelPackage pck = new ExcelPackage(filePath))
{
var schedule = pck.Workbook.Worksheets.Add("Schedule");
var cart = pck.Workbook.Worksheets.Add("Cartridge");
var unsche = pck.Workbook.Worksheets.Add("Unschedule");
var rekap = pck.Workbook.Worksheets.Add("Rekap");
pck.SaveAs(filePath);
}
using (ExcelPackage pck = new ExcelPackage(filePath))
{
var rekap = pck.Workbook.Worksheets["Rekap"];
var schedule = pck.Workbook.Worksheets["Schedule"];
rekap.Cells[4, 1].Value = "Added data";
schedule.Cells[4, 1].Value = "Added data";
pck.SaveAs(filePath);
}
As already stated, the basic code should work just fine. However, looking at your code, I sense that you are using some kind of BackgroundWorker (PengisianBaruBW_DoWork name suggests this).
If so, you might run into accessing the same file from another thread (PengisianBaruBW_DoWork executes in parallel with PrintScheduleBtn_Click).
To help you more, you should add where (what line) do you receive this error and the call stack.
[Edit]
Based on additional comments, I think of one of these scenarios:
1) PengisianBaruBW_DoWork gets called many times and sometimes it happens to do work with the file, while PrintScheduleBtn_Click is trying to do work with the same file
2) An unhandled exception in _DoWork might get swallowed and leave the file opened (highly improbable since you have a disposable context).
Either way, put a breakpoint at the start of your _DoWork and one at beginning of PrintScheduleBtn_Click and use step over (F10).
I know this an old post, but it never got solved. I ran into the same problem, but i think i found the solution for it (at least it unlocked my excel file):
excelPackage.Dispose();
excelPackage = null;
GC.Collect();

Export to excel from xlsm template

Requirement
I need to create a windows application in C# where the output is an excel file (xlsm) which is created from a template in xlsm format (contains macros).
In the template file, "IneTemplate.xlsm" there is a hidden sheet, "Data". I have to fill the sheet with data (no headings for the columns. only data) from database and save using a Save File Dialog.
What I done so far ?
I have a button. In the Button click I wrote this.
using OfficeOpenXml;
using (var ms = new MemoryStream())
{
//Default filename for new excel
String newFileName = string.Concat("ExcelExport", '(',
DateTime.Now.ToString("dd-MM-yyyy h:mm:ss tt")
.Replace(':', '_')
.Replace('/', '-')
.Replace(' ', '_'), ')', ".xlsm");
FileInfo existingFile = new FileInfo(Environment.CurrentDirectory + #"\App_Data\IneTemplate.xlsm");
if (existingFile.Exists)
{
using (var MyExcel = new ExcelPackage(existingFile))
{
ExcelWorksheet worksheet = MyExcel.Workbook.Worksheets["Data"];
int tripCount = 1;
//I have few trip data in a checked list box which I need to fill in the first column (column "A")
foreach (object item in clbTrip.CheckedItems)
{
DropDown trip = (DropDown)item;
worksheet.Cells[tripCount, 1].Value = trip.Value;
tripCount++;
}
int stopcount = 2;
int rowCount = 1;
//I have to fill the remaining columns with stops from each Trip in each column.(stops from Trip1 in column "B", from Trip 2 in "C" and so on)
foreach (object item in clbTrip.CheckedItems)
{
DropDown trip = (DropDown)item;
//Get the stops in a Trip from database
DataSet dsStopNames = objTrip.GetStopNames(trip.Id);
foreach (DataRow row in dsStopNames.Tables[0].Rows)
{
worksheet.Cells[rowCount, stopcount].Value = Convert.ToString(row["StopAliasName"]);
rowCount++;
}
stopcount++;
rowCount = 1;
}
try
{
//Create a save file Dialog
SaveFileDialog saveFileDialogExcel = new SaveFileDialog();
saveFileDialogExcel.Filter = "Excel files (*.xlsm)|*.xlsm";
saveFileDialogExcel.Title = "Export Excel File To";
saveFileDialogExcel.FileName = newFileName;
if (saveFileDialogExcel.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
if (saveFileDialogExcel.FileName != "")
{
string path = saveFileDialogExcel.FileName;
MyExcel.SaveAs(ms);
File.WriteAllBytes(path, ms.ToArray());
}
}
}
catch (Exception)
{
MessageBox.Show("Exception Occured While Saving , Check Whether the file is open");
}
}
}
}
Other Info
Used EPPlus for excel generation.
There is no Microsoft Office installed in the system.
Problem
A file got created. But when I open, Its shows file is not in proper format or corrupted or something.
Is there any other way to achieve this?
Will EPPlus able to handle xlsm files?
Please Help.

Categories

Resources