Unable to delete table using EPPlus - c#

I'm having an issue with a very basic thing. Deleting a table using EPPlus.
The problem that I'm facing is that from the code point of view (I've debugged the code, and in the Worksheet.Tables collection, the tabled that I called Delete on is gone. I proceed to save the excel and when I try to open it, Excel tells me that "they" found a problem with it, and if I want them to recover as much as possible. If I select Yes (because No closes the file) the table that I just delete through code is back again.
If I set the ClearRange property in the Delete() method to true, the table is still in the spreadsheet, but this time the header columns are gone, and instead Excel recreates my table with some default names (Column1, Column2, etc, depending on the number of columns the original table had).
I've tried to manipulate the TableXml property of the table, and it's still not working. Other than that, I've just read the Internet for about 3 hours with no apparent solution to my problem.
So, if anyone is wondering, the code for this is:
using (var package = new ExcelPackage(fileToWriteTo, templateToStartWith))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets[WORKSHEET_NAME];
worksheet.Tables.Delete("dynamicTable", true);
//worksheet.Tables.Delete("dynamicTable");
package.Save();
}
As you can see, a very basic operation that kinda fails.
If anyone knows of a solution to remove a table from a spreadsheet with EPPlus, please share. Would be much appreciated.

I have trouble with the Delete method as well. I can't even compile when I reference it half the time. Strange.
What if you delete the reference to the table via xml. If it is the only table in the ws or you know the exact order it is pretty easy to do:
[TestMethod]
public void Table_Delete_Test()
{
//http://stackoverflow.com/questions/36359047/unable-to-delete-table-using-epplus
//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.AddRange(new[]
{
new DataColumn("Col1", typeof (int)), new DataColumn("Col2", typeof (int)), new DataColumn("Col3", typeof (object))
});
for (var i = 0; i < 10; i++)
{
var row = datatable.NewRow();
row[0] = i; row[1] = i * 10; row[2] = Path.GetRandomFileName();
datatable.Rows.Add(row);
}
var fi = new FileInfo(#"c:\temp\Table_Delete_Test.xlsx");
if (fi.Exists)
fi.Delete();
//Create a worksheet with tables to later open and look for tables (note that EPPlus does not create the table objects until save)
using (var pck = new ExcelPackage(fi))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("source");
worksheet.Cells["A1"].LoadFromDataTable(datatable, true);
worksheet.Cells["A12"].LoadFromDataTable(datatable, true);
var table1 = worksheet.Tables.Add(worksheet.Cells["A1:C11"], "TableTest1");
var table2 = worksheet.Tables.Add(worksheet.Cells["A12:C22"], "TableTest2");
pck.Save();
}
//Open the test workbook and delete the second table
using (var pck = new ExcelPackage(fi))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.First();
var wsXml = worksheet.WorksheetXml;
var nsm = new XmlNamespaceManager(wsXml.NameTable);
var nsuri = wsXml.DocumentElement.NamespaceURI;
nsm.AddNamespace("m", nsuri);
//Remove reference to the second table which should be last
var tablePartsNode = wsXml.SelectSingleNode("m:worksheet/m:tableParts", nsm);
tablePartsNode.RemoveChild(tablePartsNode.LastChild);
pck.Save();
}
}
So it goes from this:
to this:
But if you need to do it by Table.Name that is not so easy. You would have to get the RelationshipID which Epplus has marked as internal. The RID is based on the zip package so to get that you either fork and modify Epplus to expose it or you open the XLSX as a ZipArchive and get the RID from that - kind of a pain.

Related

How can I create an Excel table using NPOI XSSF in C#?

I want to create a table in excel file using XSSFTable, after many research I can not do. The resources for 'NPOI in c#' on internet are very unique.
XSSFSheet sheet = (XSSFSheet)workbook.CreateSheet("Architecture");
XSSFTable table = sheet.CreateTable();
CT_Table cttable = table.GetCTTable();
cttable.displayName = "Table1";
cttable.id = 1;
cttable.name = "Test";
// cttable.setRef("A1:C11");
cttable.totalsRowShown = false;
that's what I found but I cannot complete it to insert data inside table .

Creating multiple header rows in an excel file with EPPlus.Core

I will be using EPPlus.Core to generate excel reports in my .Net Core project. So after digging Google to find some sample code snippets I found the following blog:
Create Excel Files in C#
I can create an excel file with a single header with the following code:
using (ExcelPackage excel = new ExcelPackage())
{
excel.Workbook.Worksheets.Add("Worksheet1");
excel.Workbook.Worksheets.Add("Worksheet2");
excel.Workbook.Worksheets.Add("Worksheet3");
var headerRow = new List<string[]>()
{
new string[] { "ID", "First Name", "Last Name", "DOB" }
};
// Determine the header range (e.g. A1:D1)
string headerRange = "A1:" + Char.ConvertFromUtf32(headerRow[0].Length + 64) + "1";
// Target a worksheet
var worksheet = excel.Workbook.Worksheets["Worksheet1"];
// Popular header row data
worksheet.Cells[headerRange].LoadFromArrays(headerRow);
FileInfo excelFile = new FileInfo(#"C:\Users\amir\Desktop\test.xlsx");
excel.SaveAs(excelFile);
}
But I couldn't find out how to add multiple header rows on a single worksheet. For instance, the first table has ID, Name, Price headers and the second table has Notes and Price headers. Is it possible to add multiple tables into a worksheet with EPPlus.Core? The code snippet above adds only a single header row from A1 to D1. Let's say I wanna add another header row from A4 to J4. Any suggestions?
And yet I found the answer. Hope this will help others to save their time. It is not the best of best approach but this is what I need. I defined the coordinates on the Cells array by adding "A1:J1" and "A3:E3".
var worksheet = excel.Workbook.Worksheets["Worksheet1"];
worksheet.Cells["A1:J1"].LoadFromArrays(header1Data);
worksheet.Cells["A1:J1"].Style.Font.Bold = true;
worksheet.Cells[2, 1].LoadFromArrays(array1Data);
worksheet.Cells["A3:E3"].LoadFromArrays(header2Data);
worksheet.Cells["A3:E3"].Style.Font.Bold = true;
worksheet.Cells[4, 1].LoadFromArrays(array2Data);

c# wpf importing excel performance

I'm importing a large excel file that can vary in length (250+ columns * 100,000 rows), it holds columns of data, where amount of columns and their names can change, rows are also variable but they are the values.
I'm using Interop to pull the data into a datatable which is bound to a datagrid, however I'm importing each row individually and it can take 25+ minutes to complete for larger files.
public Task<DataTable> ParseExcel(string filePath)
{
return Task.Run(() =>
{
var excelApp = new Microsoft.Office.Interop.Excel.Application();
var excelBook = excelApp.Workbooks.Open(filePath, 0, true, 5, "", "", true,
Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
var excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelBook.Worksheets.Item[1];
Microsoft.Office.Interop.Excel.Range excelRange = excelSheet.UsedRange;
DataTable sessiondt = new DataTable();
object[,] value = excelRange.Value;
int columnsCount = value.GetLength(1);
for (var colCnt = 1; colCnt <= columnsCount; colCnt++)
{
sessiondt.Columns.Add((string)value[1, colCnt], typeof(string));
}
int rowsCount = value.GetLength(0);
for (var rowCnt = 2; rowCnt <= rowsCount; rowCnt++)
{
var dataRow = sessiondt.NewRow();
for (var colCnt = 1; colCnt <= columnsCount; colCnt++)
{
dataRow[colCnt - 1] = value[rowCnt, colCnt];
}
sessiondt.Rows.Add(dataRow);
}
excelBook.Close(true);
excelApp.Quit();
return sessiondt;
});
}
Rather than inserting each row individually, it would probably be faster to put it all into a List of a custom object that can be data bound. But I'm unsure of how to do this.
Also, I want to bind the columns in a way which i don't have to code in the column names in advance. I'll be trying to display these in graphs and being able to populate the column names into a Combobox automatically would be allot easier.
Thank you in advance, I am new to c# and wpf and still learning.
Interop has some specific uses, but if you just want to get the data from an Excel file, Interop is probably the slowest and most cumbersome way to go.
An Excel file, either .xls or .xlsx can be treated and accessed just like a database.
As long as you have data in rows and columns in your worksheets, you can open an OleDb connection to it and run queries against it.
The Sheet names take the place of the table name and if you have column headings in the first row of your sheet, those are the field names.
You just need the proper connection string:
https://www.connectionstrings.com/excel/
One of the 'watch-outs' with this method of retrieving your data is that data types are automatically assigned based on the first few entries in each column. You cannot override this behavior (you used to be able to, but no longer). A time where this might cause a problem is if you have alpha-numerics in a column, and the first dozen or so entries are all numbers. This column will then be automatically assigned as a numeric type. If you have data in later rows of this column that are mixed alpha-numeric or straight text, these entries will be ignored (not imported) because they don't match the data type that was initially assigned.
The only good way around that is to programmatically unzip and parse out the contents of the xml files.
If you have consistent data throughout, then this isn't an issue.
Here is another way how you can do this, fast and straightforward, by using GemBox.Spreadsheet library:
public Task<DataTable> ParseExcel(string filePath)
{
return Task.Run(() =>
{
ExcelFile excelBook = ExcelFile.Load(filePath);
ExcelWorksheet excelSheet = excelBook.Worksheets[0];
CreateDataTableOptions options = new CreateDataTableOptions();
return excelSheet.CreateDataTable(options);
});
}
Also check this DataTable from Sheet example.

Append data to excel sheet if created using ClosedXML in c#

I am writing an application in which I need to store data cell values into excel sheet. Everything is working fine but the problem is everytime I run the application, it overwrites the existing data.
So far the code I have taken from Github:
var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sample Sheet");
worksheet.Cell("A1").Value = this.textBox1.Text;
worksheet.Cell("B1").Value = this.textBox2.Text;
worksheet.Cell("C1").Value = this.textBox3.Text;
worksheet.Cell("D1").Value = col1;
worksheet.Cell("E1").Value = col2;
worksheet.Cell("F1").Value = this.textBox6.Text;
workbook.SaveAs("HelloWorld.xlsx");
Note: I don't want to save data using datatable or anything. I just want to get values from textboxes and append them to the existing sheet. I have visited many stackoverflow post but they doesn't helped me much.
Thanks in advance!
Hope this helps:
var wb = new XLWorkbook("Path to file");
IXLWorksheet Worksheet = wb.Worksheet("Tab name");
int NumberOfLastRow = Worksheet.LastRowUsed().RowNumber();
IXLCell CellForNewData = Worksheet.Cell(NumberOfLastRow + 1, 1);
CellForNewData.InsertData(your_data);
You are explicitly storing the values in row 1 of the spreadsheet. If you want to append the values, you'll have to increment the row number and store the values in the appropriate cells.

ClosedXML excel document reported as corrupted

I'm trying to create an excel spreadsheet with the ClosedXML assembly for some master/detail stuff. I can create the flat master output fine. However, when I went back and started adding on the detail portions, the code runs fine but Excel reports the file as being corrupted.
Note, the output I'm trying to create is a simple pattern of
Master Record
[empty cell]Details
[empty cell]Details
Master Record
etc...
Here's my code:
// get "master level" order table
DataTable masterTable = ParseMaster(view, columnPlacement, hiddenItems);
masterTable.Columns.OfType<DataColumn>().ToList().ForEach(column => column.Caption = column.ColumnName);
//create workbook and add "master level" table, capturing the new worksheet in an instance variable
XLWorkbook workbook = new XLWorkbook();
IXLWorksheet sheet = workbook.Worksheets.Add(masterTable, name);
//new code
//lookup the order id column for use later on
IXLAddress orderIdAddress =
sheet.RowsUsed().First().CellsUsed().First(cell => cell.Value.ToString() == "OrderID").Address;
//loop through the inserted records in a reverse fashion
//thinking was that this would be a bit more efficient and perhaps be a bit gentler on ClosedXML's event propogation
for(int rowIdx = sheet.Rows().Count(); rowIdx > 1; rowIdx--)
{
//find the row for the current iteration
IXLRangeRow row = sheet.Range(rowIdx, 1, rowIdx, sheet.ColumnsUsed().Count()).Rows().First();
//grab the order id using the location per the above lookup
string orderId = row.Cells().First(cell => cell.Address.ColumnNumber == orderIdAddress.ColumnNumber).Value.ToString();
//get the relevant detail from an object out of scope from this example
Detail det = view.Orders.First(order => order.OrderID == orderId);
//perform a similar parsing of its contents returning a datatable extremely similar to the "master level" table above
DataTable detailTable = ParseDetail(view, det);
//add new rows to the sheet to prevent overruns and bleed into other rows
IXLRangeRows newRows = row.InsertRowsBelow(detailTable.Rows.Count + 1);
//add the detail table table to the first cell of the newly added rows
newRows.First().Cell(1).InsertTable(detailTable);
}
//end new code
using (MemoryStream stream = new MemoryStream())
{
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry entry = archive.CreateEntry(name + ".xlsx", CompressionLevel.Fastest);
using (Stream entryStream = entry.Open())
{
workbook.SaveAs(entryStream);
}
}
return stream.ToArray();
}
I'm sure something I'm doing isn't jiving well with the OpenXML format but I'm a bit of a noob at this and the ClosedXML documentation doesn't have anything that's jumping out at me.

Categories

Resources