C# EPPlus merge Excel Files - c#

I want to merge multiple Excel files with EPPlus in C#.
I did the following:
using (MemoryStream protocolStream = new MemoryStream())
{
ExcelPackage pck = new ExcelPackage();
HashSet<string> wsNames = new HashSet<string>();
foreach (var file in files)
{
ExcelPackage copyPck = new ExcelPackage(new FileInfo(file));
foreach (var ws in copyPck.Workbook.Worksheets)
{
string name = ws.Name;
int i = 1;
while (!wsNames.Add(ws.Name))
name = ws.Name + i++;
ws.Name = name;
var copiedws = pck.Workbook.Worksheets.Add(name);
copiedws.WorksheetXml.LoadXml(ws.WorksheetXml.DocumentElement.OuterXml);
}
}
pck.SaveAs(protocolStream);
protocolStream.Position = 0;
using (FileStream fs = new FileStream(resultFile, FileMode.Create))
protocolStream.CopyTo(fs);
}
But I get the following error in pck.SaveAs(protocolStream):
System.ArgumentOutOfRangeException
in
System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument
argument, ExceptionResource resource) in
System.Collections.Generic.List1.get_Item(Int32 index) in
OfficeOpenXml.ExcelStyleCollection1.get_Item(Int32 PositionID)
I also tried it with the Worksheet.Copy method, but I lose the styling with it.

Here is an example of merging several files into one by coping all worksheets from source excel files.
var files = new string[] { #"P:\second.xlsx", #"P:\second.xlsx" };
var resultFile = #"P:\result.xlsx";
ExcelPackage masterPackage = new ExcelPackage(new FileInfo(#"P:\first.xlsx"));
foreach (var file in files)
{
ExcelPackage pckg = new ExcelPackage(new FileInfo(file));
foreach (var sheet in pckg.Workbook.Worksheets)
{
//check name of worksheet, in case that worksheet with same name already exist exception will be thrown by EPPlus
string workSheetName = sheet.Name;
foreach (var masterSheet in masterPackage.Workbook.Worksheets)
{
if (sheet.Name == masterSheet.Name)
{
workSheetName = string.Format("{0}_{1}", workSheetName, DateTime.Now.ToString("yyyyMMddhhssmmm"));
}
}
//add new sheet
masterPackage.Workbook.Worksheets.Add(workSheetName, sheet);
}
}
masterPackage.SaveAs(new FileInfo(resultFile));

Related

Slow building excel worksheet using NPOI

I am using the NPOI library to build Excel worksheets in an app that my supervisor wants to host on a Linux server. The app works fine with files under 1000 rows but occasionally I have large files and it takes a lot longer than I would expect. Does anyone have any idea how I can speed up the processing for these large files? Thanks.
Here is the code:
foreach (DataRow row in dataMalformed.Rows)
{
IWorkbook workbook = new XSSFWorkbook();
//Console.WriteLine(row[0]);
if (dataMalformed.Rows.Count > 0)
{
strFileName = strMalformed + "\\MVPSMalformedProd" + "-";
strFileName2 = strFileName + strDate2 + ".xls";
}
var fs = new FileStream(strFileName, FileMode.Create, FileAccess.ReadWrite);
var fs2 = new FileStream(strFileName2, FileMode.Create, FileAccess.ReadWrite);
strSheetName = "Malformed Messages - " + ", " + strDate2;
using (fs)
{
ISheet excelSheet = workbook.CreateSheet(strSheetName);
intRow = 2;
List<String> columns = new List<string>();
IRow row1 = excelSheet.CreateRow(0);
int columnIndex = 0;
foreach (System.Data.DataColumn column in dataMalformed.Columns)
{
columns.Add(column.ColumnName);
row1.CreateCell(columnIndex).SetCellValue(column.ColumnName);
columnIndex++;
}
foreach (DataRow row2 in dataMalformed.Rows)
{
//Console.WriteLine(row2[0]);
row1 = excelSheet.CreateRow(intRow);
int cellIndex = 0;
foreach (String col in columns)
{
row1.CreateCell(cellIndex).SetCellValue(row2[col].ToString());
excelSheet.AutoSizeColumn(cellIndex);
cellIndex++;
}
if (dictSenders.ContainsKey(row2[3].ToString()))
{
row1.CreateCell(--cellIndex).SetCellValue(dictSenders[row2[3].ToString()]);
}
intRow++;
}
}
And you are sure that NPOI is the problem? I just tried the following to create an excel file with 10000 rows and 50 columns.
var workbook = new XSSFWorkbook ();
var sheet = workbook.CreateSheet ("Sheet1");
for (var rowNum = 0; rowNum < 10000; rowNum++)
{
var rowData = sheet.CreateRow (rowNum);
for (var columnNum = 0; columnNum < 50; columnNum++)
{
rowData.CreateCell (columnNum).SetCellValue ($"Row {rowNum + 1}, Column {columnNum + 1}");
}
}
using (FileStream fileStream = File.Create ("D:\\Temp\\test.xlsx"))
{
workbook.Write (fileStream);
fileStream.Close ();
}

C# Problem opening excel file using `OpenXml` from `byte[]`

I have a problem when saving and opening a file with OpenXml library. Here is my code:
public static void SaveExcel(List<Dictionary<string, object>> listData, List<string> entityTypes, string appName)
{
using (var ms = new MemoryStream())
using (var excel = SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook))
{
var workBookPart = excel.AddWorkbookPart();
workBookPart.Workbook = new Workbook();
var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
var workSheetData = new SheetData();
workSheetPart.Worksheet = new Worksheet(workSheetData);
var sheets = workBookPart.Workbook.AppendChild(new Sheets());
var index = 1;
foreach (var entityType in entityTypes)
{
var sheet = new Sheet
{
Id = excel.WorkbookPart.GetIdOfPart(workSheetPart),
SheetId = 1U,
Name = entityType
};
sheets.AppendChild(sheet);
}
workBookPart.Workbook.Save(ms);
File.WriteAllBytes("D:/nothing123.xlsx", ms.ToArray());
}
}
I am pretty sure I did the right thing though I have this error when opening the file:
Excel cannot be opened the file 'nothing123.xlsx' because the file format or file extension is not valid. Verify that the file has not been corrupted and the file extension matches the format of the file.
Any idea whats going on with my code?
I don't know if this is relevant to the tag but I made up my mind to use ClosedXml library since it's much easier to use than OpenXml. I can easily create a DataTable and create an Excel file out of the DataTable which is very convenient. Here is my quick sample code:
Sample Data Table
public DataTable getData() {
DataTable dt = new DataTable();
dt.TableName = "SheetName1";
dt.Columns.Add("FirstName");
dt.Columns.Add("LastName");
var row = dt.NewRow();
row["FirstName"] = "Alvin";
row["LastName"] = "Quezon";
dt.Rows.Add(row);
return dt;
}
Sample Code to Excel to Byte Array
public static byte[] GetExcelBytes(DataTable dataTable)
{
using (var ms = new MemoryStream())
using (var workBook = new XLWorkbook())
{
workBook.Worksheets.Add(dataTable);
workBook.SaveAs(ms);
return ms.ToArray();
}
}
I didn't get any issue when opening the file and with superb minimal code usage.
Hopefully this will help anyone would like to use this in the future.

EPPlus:Cannot access a closed Stream when copy existing excel worksheet

I'm currently using EPPlus and right now I'm trying to copy existing worksheet from an excel file and add the copied worksheet to another excel file. But then, Ive encounter this error:
Full Stack Trace
System.ObjectDisposedException: Cannot access a closed Stream.
at System.IO.MemoryStream.Seek(Int64 offset, SeekOrigin loc)
at OfficeOpenXml.Packaging.ZipPackagePart.GetStream(FileMode fileMode, FileAccess fileAccess)
at OfficeOpenXml.Packaging.ZipPackagePart.WriteZip(ZipOutputStream os)
at OfficeOpenXml.Packaging.ZipPackage.Save(Stream stream)
at OfficeOpenXml.ExcelPackage.Save()
at OfficeOpenXml.ExcelPackage.SaveAs(Stream OutputStream)
Excel Helper
public byte[] ExportStraightBoxClampPreview(PreviewInputDto input)
{
//the path of the file
string filePath = #"C:\Preview\WeldedStraightBox.xlsx";
string filePath1 = #"C:\Preview\Strongback.xlsx";
//create a fileinfo object of an excel file on the disk
FileInfo file = new FileInfo(filePath);
FileInfo file1 = new FileInfo(filePath1);
FileInfo file2 = new FileInfo(filePath2);
//create a new Excel package from the file
using (ExcelPackage strongBack = new ExcelPackage(file1))
using (ExcelPackage excelPackage = new ExcelPackage(file))
{
ExcelWorksheet wsSrc = strongBack.Workbook.Worksheets.First();
ExcelWorksheet wsDest = excelPackage.Workbook.Worksheets[wsSrc.Name] ?? excelPackage.Workbook.Worksheets.Add(wsSrc.Name, wsSrc);
** error here **------> return excelPackage.GetAsByteArray();
}
Is it because I do not use Memory Stream? But then If I manually copied using looping for each row and column, it is working:
another method
ExcelWorksheet wsSrc = strongBack.Workbook.Worksheets.First();
ExcelWorksheet wsDest = excelPackage.Workbook.Worksheets[wsSrc.Name] ?? excelPackage.Workbook.Worksheets.Add(wsSrc.Name);
for (var r = 1; r <= wsSrc.Dimension.Rows; r++)
{
for (var c = 1; c <= wsSrc.Dimension.Columns; c++)
{
var cellSrc = wsSrc.Cells[r, c];
var cellDest = wsDest.Cells[r, c];
// Copy value
cellDest.Value = cellSrc.Value;
// Copy cell properties
cellDest.Style.Numberformat = cellSrc.Style.Numberformat;
cellDest.Style.Font.Bold = cellSrc.Style.Font.Bold;
// TODO... Add any additional properties that you may want to copy over
}
}
I'm really sorry for the lack of knowledge about this. I'm really appreciate any help.

Swap worksheets within the excel workbook c#

For example, see the image
I want to swap the worksheet "Sheet1" to "Sheet3".
My Code using EPPlus:
ExcelPackage masterPackage = new ExcelPackage();
foreach (var file in files)
{
ExcelPackage pckg = new ExcelPackage(new FileInfo(file));
foreach (var sheet in pckg.Workbook.Worksheets)
{
//check name of worksheet, in case that worksheet with same name already exist exception will be thrown by EPPlus
string workSheetName = sheet.Name;
foreach (var masterSheet in masterPackage.Workbook.Worksheets)
{
if (sheet.Name == masterSheet.Name)
{
workSheetName = string.Format("{0}_{1}", workSheetName, DateTime.Now.ToString("yyyyMMddhhssmmm"));
}
}
//add new sheet
if (sheet.Name.Contains("MB_STORE_POTENTIALvsWALLET"))
{
masterPackage.Workbook.Worksheets.Add(workSheetName, sheet);
}
else
{
masterPackage.Workbook.Worksheets.Add(workSheetName, sheet);
masterPackage.Workbook.Worksheets.MoveToStart(1);
}
}
}
masterPackage.SaveAs(new FileInfo(resultFile));
How to do this? Any suggestion please..
If only you need to swap the sheets (I mean content do not required to be processed) then renaming sheet should be simple.
Rename the "Sheet1" to "adsf"
Rename the "Sheet3" to "Sheet1"
Rename the "adsf" to "Sheet3"
Sheets("Sheet1").Name = "adsf"
Sheets("Sheet3").Name = "Sheet1"
Sheets("adsf").Name = "Sheet3"
This is working fine:
ExcelPackage masterPackage = new ExcelPackage();
foreach (var file in files)
{
ExcelPackage pckg = new ExcelPackage(new FileInfo(file));
foreach (var sheet in pckg.Workbook.Worksheets)
{
//check name of worksheet, in case that worksheet with same name already exist exception will be thrown by EPPlus
string workSheetName = sheet.Name;
foreach (var masterSheet in masterPackage.Workbook.Worksheets)
{
if (sheet.Name == masterSheet.Name)
{
workSheetName = string.Format("{0}_{1}", workSheetName, DateTime.Now.ToString("yyyyMMddhhssmmm"));
}
}
//add new sheet
if (sheet.Name.Contains("MB_STORE_POTENTIALvsWALLET"))
{
masterPackage.Workbook.Worksheets.Add(workSheetName, sheet);
}
else
{
masterPackage.Workbook.Worksheets.Add(workSheetName, sheet);
masterPackage.Workbook.Worksheets.MoveBefore(2, 1);
}
}
}
masterPackage.SaveAs(new FileInfo(resultFile));

How to Read the Uploaded Excel File using NPOI with out storing in the server or in the Project

My sample Code is below using the NPOI.dll. I can read the EXCEL File (i.e. Excel would be stored in the system like D:/Jamal/Test.xls. Then the dll is easily reading the content, but I need to read the uploaded Excel file without storing it in any place before.The HTTPPOSTEDFILEBASE excelfile has the value for the Excel file but I need to know how to read it using NPOI dlls
public List<string> SendInvitesExcelFile1(List<String> CorrectMailIDs,
ListInvites Invites, HttpPostedFileBase excelfile)
{
List<string> mailids = new List<string>();
//string filename = (excelfile.FileName).ToString();
HSSFWorkbook hssfwb;
// using (FileStream file = new FileStream(#"D:\test.xls", FileMode.Open, FileAccess.Read))
using (FileStream file = new FileStream(excelFile.FileName, FileMode.Open, FileAccess.Read))
{
hssfwb = new HSSFWorkbook(file);
}
Sheet sheet = hssfwb.GetSheet("sheet1");
for (int row = 0; row <= sheet.LastRowNum; row++)
{
if (sheet.GetRow(row) != null) //null is when the row only contains empty cells
{
mailids.Add(sheet.GetRow(row).GetCell(0).ToString());
}
}
return mailids;
I came across the same problem and I solved it using Inpustream. I am pasting the code for your reference.
[HttpPost]
public DataTable PostValues(HttpPostedFileBase file)
{
ISheet sheet;
string filename = Path.GetFileName(Server.MapPath(file.FileName));
var fileExt = Path.GetExtension(filename);
if (fileExt == ".xls")
{
HSSFWorkbook hssfwb = new HSSFWorkbook(file.InputStream);
sheet = hssfwb.GetSheetAt(0);
}
else
{
XSSFWorkbook hssfwb = new XSSFWorkbook(file.InputStream);
sheet = hssfwb.GetSheetAt(0);
}
DataTable table = new DataTable();
IRow headerRow = sheet.GetRow(0);
int cellCount = headerRow.LastCellNum;
for (int i = headerRow.FirstCellNum; i < cellCount; i++)
{
DataColumn column = new DataColumn(headerRow.GetCell(i).StringCellValue);
table.Columns.Add(column);
}
int rowCount = sheet.LastRowNum;
for (int i = (sheet.FirstRowNum); i < sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
DataRow dataRow = table.NewRow();
for (int j = row.FirstCellNum; j < cellCount; j++)
{
if (row.GetCell(j) != null)
{
dataRow[j] = row.GetCell(j).ToString();
}
}
table.Rows.Add(dataRow);
}
return table;
}
You can use a MemoryStream as well, so you should be able to get the byte array repsonse and open the spreadsheet. I'm unsure if the WorkbookFactory will detect the file type from the MemoryStream, so you may need to instruct the users to use the format that you require.
Here is how I use a MemoryStream to return a xls from a dot net core controller without ever storing it as a file.
IWorkbook wb = new HSSFWorkbook();
//Edit workbook object here
MemoryStream m = new MemoryStream();
wb.Write(m);
var byteArray = m.ToArray();
return new FileContentResult(byteArray, "application/vnd.ms-excel");

Categories