How to aggregate two worksheets to one workbook using openXML? - c#

I want to aggregate two different worksheet from another workbooks to one workbook but I don't know how to do that using openXML. I only want to create one workbook with two worksheet. I don't need merge worksheets to one. How to aggregate two worksheets to one workbook using openXML?

Copying a worksheet from one workbook to another is easy with Epplus which is available free in Nuget.
Something like this example would copy a worksheet & all it's data from one workbook to another in one go without the need for any separate function to loop over the rows to copy the data:
FileInfo fInfoSrc = new FileInfo(#"C:\Temp\Source.xlsx");
FileInfo fInfoDest = new FileInfo(#"C:\Temp\Destination.xlsx");
using (var source = new ExcelPackage(fInfoSrc))
{
using (var destination = new ExcelPackage(fInfoDest))
{
var srcWorksheet = source.Workbook.Worksheets["SourceWorksheet"];
var destWorksheet = destination.Workbook.Worksheets.Add("destinationWorksheetName", srcWorksheet);
destination.Save();
}
}

You need a reference to the OpenXml SDK.
A small example how to create a workbook.
Call the second method AddWorksheet as many worksheets you need.
private static SpreadsheetDocument CreateWorkbook(Stream stream)
{
// Create the Excel workbook
var spreadSheet = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook, false);
// Create the parts and the corresponding objects
// Workbook
spreadSheet.AddWorkbookPart();
spreadSheet.WorkbookPart.Workbook = new Workbook();
spreadSheet.WorkbookPart.Workbook.Save();
// Shared string table
var sharedStringTablePart = spreadSheet.WorkbookPart.AddNewPart<SharedStringTablePart>();
sharedStringTablePart.SharedStringTable = new SharedStringTable();
sharedStringTablePart.SharedStringTable.Save();
// Sheets collection
spreadSheet.WorkbookPart.Workbook.Sheets = new Sheets();
spreadSheet.WorkbookPart.Workbook.Save();
// Stylesheet
var workbookStylesPart = spreadSheet.WorkbookPart.AddNewPart<WorkbookStylesPart>();
workbookStylesPart.Stylesheet = new Stylesheet();
workbookStylesPart.Stylesheet.Save();
return spreadSheet;
}
private static WorksheetPart AddWorksheet(SpreadsheetDocument spreadsheet, string name)
{
// Add the worksheetpart
var worksheetPart = spreadsheet.WorkbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
uint sheetId = 1;
var sheets = spreadsheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
if (sheets.Elements<Sheet>().Any())
{
sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
}
// Add the sheet and make relation to workbook
var sheet = new Sheet
{
Id = spreadsheet.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = sheetId,
Name = name
};
sheets.Append(sheet);
worksheetPart.Worksheet.Save();
spreadsheet.WorkbookPart.Workbook.Save();
return worksheetPart;
}

The best way which I chose is to open destination file and in loop open iteratively source file with worksheet to be copied. Next, I clone each row from source to destination file with clone node method with deep mode. This cloned row I insert in specific index in destination file worksheet.
The good way is to use EPPlus like in other answer to this question but when I use it with excel file where is defined name (named ranges) it does not work correctly.

Related

Creating a dynamic number of workbooks using EPPlus

I have what is essentially a database with tables owned by different groups within our larger group. I have developed an app that helps make Excel files we use to edit these tables that breaks out the tables by what group owns the table and creates an Excel workbook using EPPlus. The owners are defined in a csv file that I read in and assign to each table as I go through them. In each workbook it makes a tab for each tab that the group owns. So right now a simplified version of my code looks like:
using (ExcelPackage grp1 = new ExcelPackage())
{
using (ExcelPackage grp2 = new ExcelPackage())
{
using (ExcelPackage grp3 = new ExcelPackage())
{
// iterate thru list of tables and owners
foreach (string table in tablelist)
{
if (tableowner = group1)
{
ExcelWorksheet ws = grp1.Workbook.Worksheets.Add(currenttable.Name);
// do stuff
}
if (tableowner = group2)
{
ExcelWorksheet ws = grp2.Workbook.Worksheets.Add(currenttable.Name);
// do stuff
}
if (tableowner = group3)
{
ExcelWorksheet ws = grp3.Workbook.Worksheets.Add(currenttable.Name);
// do stuff
}
if (grp1.Workbook.Worksheets.Count > 0)
{
FileInfo grp1fi = new FileInfo(path);
grp1.SaveAs(grp1fi);
}
if (grp2.Workbook.Worksheets.Count > 0)
{
FileInfo grp2fi = new FileInfo(path);
grp2.SaveAs(grp2fi);
}
if (grp3.Workbook.Worksheets.Count > 0)
{
FileInfo grp3fi = new FileInfo(path);
grp3.SaveAs(grp3fi);
}
}
}
}
}
This works great as long as everything is only put into those 3 groups that I have the code in place for. But I'm running into the need to let the user define the groups to however many groups they need. So I need to be able to dynamically create the correct number of Excel files. Is this possible? I'm guessing if it is I'll have to find the number of unique groups in the csv file that I read in first. Then create the same number of lists in which to store the tables broken out by group. From there I could iterate through one of the lists and use a generic ExcelPackage that I dispose of after looping through all the tables for that group. Then move on to the second list, etc. I'm just not sure the best way to accomplish this.
You can do all of this in a simple for loop right? Or am I misunderstanding the question?
int NoOfWorkBooks = 5;
for (int i = 0; i < NoOfWorkBooks; i++)
{
using (ExcelPackage grp = new ExcelPackage())
{
if (tableowner == group)
{
ExcelWorksheet ws = grp.Workbook.Worksheets.Add(currenttable.Name);
FileInfo grp1fi = new FileInfo(string.Format(#"c:\temp\ExcelFile_{0}.xlsx", i));
grp.SaveAs(grp1fi);
}
}
}

How to append a new row to an Excel file using C# and ClosedXML?

I should append a new row to an existing Excel file. The task consists of two parts:
Add to non-existing file (works well).
Add to existing file (doesn't work: it doesn't make NEW record, displaying only the old record from "else" body).
Here is my code:
private static void ExportToEXCEL(DataTable dt, string paymentStoryPath)
{
if (File.Exists(paymentStoryPath))
{
XLWorkbook currentWorkbook = new XLWorkbook(paymentStoryPath);
IXLWorksheet currentWsh = currentWorkbook.Worksheet("Payment history");
//IXLCell cellForNewData = index.Cell(index.LastRowUsed().RowNumber() + 1, 1);
IXLRow rowForNewData = currentWsh.Row(currentWsh.LastRowUsed().RowNumber()+1);
rowForNewData.InsertRowsBelow(1);
rowForNewData.Value = dt;
currentWorkbook.Save();
}
else
{
//not exist
XLWorkbook wb = new XLWorkbook();
wb.Worksheets.Add(dt, "Payment history");
wb.SaveAs(paymentStoryPath);
}
}
What is wrong and what should I change in my code?
To add a DataTable use the InsertTable() method:
XLWorkbook currentWorkbook = new XLWorkbook(paymentStoryPath);
IXLWorksheet currentWsh = currentWorkbook.Worksheet("Payment history");
IXLCell cellForNewData = currentWsh.Cell(currentWsh.LastRowUsed().RowNumber() + 1, 1);
cellForNewData.InsertTable(dt);
currentWorkbook.Save();
I've got the following code from one of my projects that inserts a DataTable into Excel.
//insert rows below a range from the cell going table rows down
ws.Range(
cell.Address.RowNumber
, cell.Address.ColumnNumber
, cell.Address.RowNumber + DocDataSet.Tables[tableNo].Rows.Count
, cell.Address.ColumnNumber)
.InsertRowsBelow(DocDataSet.Tables[tableNo].Rows.Count);
//InsertData returns a range covering the inserted data
var ra = ws.Cell(cell.Address.RowNumber, cell.Address.ColumnNumber)
.InsertData(DocDataSet.Tables[tableNo].AsEnumerable());
//apply the style of the table token cell to the whole range
ra.Style = cell.Style;
Its been a while since I wrote it, but as far as I know the idea is, create a range that will cover the rows and columns that will be populated. The Cell object has a InsertData method that can take any IEnumerable source.
You might not need the ws.Range line, I was inserting into a template so I had to create the space first.
I took #raidri example and took it one step further where I have an extension method to handle this.
public static class Extensions
{
public static void ToExcelFile(this DataTable dataTable, string filename, string worksheetName = "Sheet1")
{
using(var workbook = new XLWorkbook())
{
workbook.Worksheets.Add(dataTable, worksheetName);
workbook.SaveAs(filename);
}
}
}
Use
myDataTable.ToExcelFile(#"C:\temp\myFile.xlsx");

How to add values to a spreadsheet from a dictionary?

I have a template spreadsheet document that has two columns, Server Name and IP Address.
How can I populate the spreadsheet so that each dictionary key goes in its own cell in the Server column and the corresponding value goes in the cell next to it in the IP column?
I am using the EPPlus library but couldn't find anything on the topic.
Below is what I found and tried, but its for lists
using (ExcelPackage package = new ExcelPackage(_fileInfo))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
for (int i = 0; i < listOfIPs.Count; i++)
{
worksheet.Cells[i + 2, 1].Value = listOfIPs[i];
}
package.Save();
}
I am not familiar with EPPlus, therefore I am not sure how you get the reference to the active sheet - you need to figure out this bit though once that's done pure C# with a bit of knowledge about VBA model and you can easily avoid any iterations to get contents of your dictionary to a spreadsheet:
// create a sample dictionary and fill it
Dictionary<string, string> myCol = new Dictionary<string, string>();
myCol.Add("server1", "ip1");
myCol.Add("server2", "ip2");
// grab a reference to the active Sheet
// this may vary depending on what framework you are using
Worksheet ws = Globals.ThisWorkbook.ActiveSheet as Worksheet;
// create a Range variable
Range myRange;
// Transpose the keys to column A
myRange = ws.get_Range("A1");
myRange.get_Resize(myCol.Keys.ToArray().Count(),1).Value =
ws.Parent.Parent.Transpose(myCol.Keys.AsEnumerable().ToArray());
// transpose the Values to column B
myRange = ws.get_Range("B1");
myRange.get_Resize(myCol.Values.ToArray().Count(), 1).Value =
ws.Parent.Parent.Transpose(myCol.Values.AsEnumerable().ToArray());
Debugging results as expected
With EPPlus I think you can do it like this (untested)
using (ExcelPackage package = new ExcelPackage(file))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("test");
worksheet.Cells["A1"].LoadFromCollection(myColl, true, OfficeOpenXml.Table.TableStyles.Medium);
package.Save();
}
More details on VBA Collections iterations and printing to Sheet # vba4all.com
You can just access the keys of a dictionary and iterate over them just like you are iterating over the list.
var foo = new Dictionary<string, string>(); // populate your dictionary
foreach(var key in foo.Keys)
{
var value = foo[key];
// do stuff with 'key' and 'value', like put them into the excel doc
}

Get Tables (workparts) of a sheet of excel by OpenXML SDK

I have 3 tables in a sheet of excel file,
and I use OpenXML SDK to read the Excel file, like this:
SpreadSheetDocument document = SpreadSheetDDocument.open(/*read it*/);
foreach(Sheet sheet in document.WorkbookPart.Workbook.Sheets)
{
//I need each table or work part of sheet here
}
So as you see I can get each sheet of Excel, but how can I get workparts in each sheet, like my 3 tables I should can iterate on these tables, does any one know about this? any suggestion?
Does this help?
// true for editable
using (SpreadsheetDocument xl = SpreadsheetDocument.Open("yourfile.xlsx", true))
{
foreach (WorksheetPart wsp in xl.WorkbookPart.WorksheetParts)
{
foreach (TableDefinitionPart tdp in wsp.TableDefinitionParts)
{
// for example
// tdp.Table.AutoFilter = new AutoFilter() { Reference = "B2:D3" };
}
}
}
Note that the actual cell data is not in the Table object, but in SheetData (under Worksheet of the WorksheetPart). Just so you know.
You can get the specific table from excel. Adding more to the answer of #Vincent
using (SpreadsheetDocument document= SpreadsheetDocument.Open("yourfile.xlsx", true))
{
var workbookPart = document.WorkbookPart;
var relationsShipId = workbookPart.Workbook.Descendants<Sheet>()
.FirstOrDefault(s => s.Name.Value.Trim().ToUpper() == "your sheetName")?.Id;
var worksheetPart = (WorksheetPart)workbookPart.GetPartById(relationsShipId);
TableDefinitionPart tableDefinitionPart = worksheetPart.TableDefinitionParts
.FirstOrDefault(r =>
r.Table.Name.Value.ToUpper() =="your Table Name");
QueryTablePart queryTablePart = tableDefinitionPart.QueryTableParts.FirstOrDefault();
Table excelTable = tableDefinitionPart.Table;
var newCellRange = excelTable.Reference;
var startCell = newCellRange.Value.Split(':')[0]; // you can have your own logic to find out row and column with this values
var endCell = newCellRange.Value.Split(':')[1];// Then you can use them to extract values using regular open xml
}

OpenXML (SAX Method) - Adding row to existing tab

I am trying to create an Excel document using OpenXML (SAX method). When my method is called I want to check to see if a tab has already been created for a given key. If it is I would like to just append a row to the bottom of that tab. If the tab hasn't been created for a given key I create a new tab like;
part = wbPart.AddNewPart<WorksheetPart>();
string worksheetName = row.Key[i].ToString();
Sheet sheet = new Sheet() { Id = document.WorkbookPart.GetIdOfPart(part), SheetId = sheetNumber, Name = worksheetName };
sheets.Append(sheet);
writer = OpenXmlWriter.Create(part);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
currentrow = 1;
string header = Header + "\t" + wrapper.GetHeaderString(3, 2, -1); //need to fix
WriteDataToExcel(header, currentrow, 0, writer);
currentrow++;
writer.WriteEndElement();
writer.WriteEndElement();
writer.Close();
If the a tab as already been created I recall sheet using the following code;
private static WorksheetPart GetWorksheetPartByName(SpreadsheetDocument document, string sheetName)
{
IEnumerable<Sheet> sheets =
document.WorkbookPart.Workbook.GetFirstChild<Sheets>().
Elements<Sheet>().Where(s => s.Name == sheetName);
if (sheets.Count() == 0)
{
// The specified worksheet does not exist.
return null;
}
string relationshipId = sheets.First().Id.Value;
WorksheetPart worksheetPart = (WorksheetPart)
document.WorkbookPart.GetPartById(relationshipId);
return worksheetPart;
}
When the correct Worksheet part is returned I try and add the new row by pointing my OpenXmlWriter to the correct part then adding the row;
part = GetWorksheetPartByName(document, row.Key[i].ToString());
writer = OpenXmlWriter.Create(part);
writer.WriteStartElement(part.Worksheet);
writer.WriteStartElement(part.Worksheet.GetFirstChild<SheetData>());
SheetData sheetData = part.Worksheet.GetFirstChild<SheetData>();
Row lastRow = sheetData.Elements<Row>().LastOrDefault();
The code runs however I always end up with just one row (the initial one I added when first creating the tab). No subsequent rows show up in the spreadsheet.
I will be adding a lot of rows (50,000+) and would prefer not to have to create a new file and copy the information over each time.
From my experience, using the SAX method to write (ie, with OpenXmlWriter) works best for new things (parts, worksheets, whatnot). When you use OpenXmlWriter.Create(), that's like overwriting the original existing data for the part (WorksheetPart in this case). Even though in effect, it's not. It's complicated.
As far as my experiments went, if there's existing data, you can't edit data using OpenXmlWriter. Not even if you use the Save() function or close the OpenXmlWriter correctly. For some reason, the SDK will ignore your efforts. Hence the original one row that you added.
If you're writing 50,000 rows, it's best to do so all at one go. Then the SAX method will be useful. Besides, if you're writing one row (at a time?), the speed benefits of using SAX versus the DOM method is negligible.
According to this site work with exist Excel with OpenXMLWriter :
OpenXMLWriter can only operate a new Worksheet instead of an existing document. So I'm afraid you cannot insert values into particular cells of existing spreadsheet using OpenXMLWriter.
You could read all data in your exist Excel file , then seems you need to add rows(50,000+) I recommend use openxmlwriter to write old and new data to a new Excel file at once. If you use DOM approach it might cause memory problem after you append a lot of rows(50,000+).

Categories

Resources