I'm currently trying to export some data from my database to an Excel file. Everything goes fine except for one column.
I've got two date fields in my targeted table (start and end date) which I want to display in my Excel sheet. The thing is, the end date is correctly displayed (dd/mm/yyyy) while my start date isn't (mm/dd/yyyy). Their definition are absolutely the same at the database level.
Here's how I'm building my DataTable :
public System.Data.DataTable ExportLastChangesToExcel()
{
List<HD_DISCOUNTS> listDiscount = (from d in dbHosp.HD_DISCOUNTS where d.TO_EXTRACT == "N" orderby d.EXTRACT_TEXT descending select d).ToList();
System.Data.DataTable table = new System.Data.DataTable();
table.Columns.Add("Condition Type", typeof(string));
table.Columns.Add("Sales organisation", typeof(string));
table.Columns.Add("Distribution Channel", typeof(string));
table.Columns.Add("Customer", typeof(string));
table.Columns.Add("Customer Name", typeof(string));
table.Columns.Add("Material", typeof(string));
table.Columns.Add("Material name", typeof(string));
table.Columns.Add("Amount", typeof(string));
table.Columns.Add("Unit", typeof(string));
table.Columns.Add("C..", typeof(string));
table.Columns.Add("Valid From", typeof(string));
table.Columns.Add("Valid To", typeof(string));
table.Columns.Add("Valid Action", typeof(string));
var allCust = (from v in dbCust.CUSTOMER_MASTER where v.INCLUDE_INTO_HD == "Y" select v).ToList();
foreach (HD_DISCOUNTS disc in listDiscount)
{
string compareId = disc.ID_HOSP.ToString();
CUSTOMER_MASTER cust = (from h in allCust where h.SOLDTOPARTY.TrimStart('0') == compareId select h).FirstOrDefault();
DIM_PRODUCT prod = (from p in db.DIM_PRODUCT where p.P_KEY_AZ == disc.ID_PRODUCT select p).FirstOrDefault();
DIM_PRODUCT_SAP sap = (from s in db.DIM_PRODUCT_SAP where s.P_KEY_AZ == prod.P_KEY_AZ && s.P_TYPE_SAP == "Domestic" select s).FirstOrDefault();
table.Rows.Add("ZD22", "BE10", "1", cust.SOLDTOPARTY, cust.SOLDTOPARTY_DESC, sap.P_KEY_SAP.ToString(), prod.P_DESC_AZ, disc.DISCOUNT.ToString(), "%", "A", disc.START_DATE.Value.ToString("dd/MM/yyyy"), disc.END_DATE.Value.ToString("dd/MM/yyyy"), disc.EXTRACT_TEXT);
}
return table;
}
I have debugged and start date should be well displayed. Here's the code I use to build my Excel :
protected void btnExport_Click(object sender, EventArgs e)
{
Microsoft.Office.Interop.Excel.Application excel;
Microsoft.Office.Interop.Excel.Workbook worKbooK;
Microsoft.Office.Interop.Excel.Worksheet worksheet;
Microsoft.Office.Interop.Excel.Range celLrangE;
try
{
excel = new Microsoft.Office.Interop.Excel.Application();
excel.Visible = false;
excel.DisplayAlerts = false;
worKbooK = excel.Workbooks.Add(Type.Missing);
worksheet = (Microsoft.Office.Interop.Excel.Worksheet)worKbooK.ActiveSheet;
worksheet.Name = "Sheet1";
System.Data.DataTable tab = ExportLastChangesToExcel();
Range excelRange = worksheet.get_Range("K1");
excelRange.NumberFormat = "dd/mm/yyyy;#";
int rowcount = 2;
foreach (DataRow datarow in tab.Rows)
{
rowcount += 1;
for (int i = 1; i <= tab.Columns.Count; i++)
{
if (rowcount == 3)
{
worksheet.Cells[1, i] = tab.Columns[i - 1].ColumnName;
worksheet.Cells.Font.Color = System.Drawing.Color.Black;
}
worksheet.Cells[rowcount-1, i] = datarow[i - 1].ToString();
//worksheet.Cells[i, 11].NumberFormat = "#";
if (rowcount > 3)
{
if (i == tab.Columns.Count)
{
if (rowcount % 2 == 0)
{
celLrangE = worksheet.Range[worksheet.Cells[rowcount, 1], worksheet.Cells[rowcount, tab.Columns.Count]];
}
}
}
}
}
celLrangE = worksheet.Range[worksheet.Cells[1, 1], worksheet.Cells[rowcount, tab.Columns.Count]];
celLrangE.EntireColumn.AutoFit();
celLrangE = worksheet.Range[worksheet.Cells[1, 1], worksheet.Cells[2, tab.Columns.Count]];
worKbooK.SaveAs("C:\\Temp\\Hospital Discount\\Genpact\\GenpactExtract.xlsx");
worKbooK.Close();
excel.Quit();
}
catch (Exception ex)
{
}
finally
{
worksheet = null;
celLrangE = null;
worKbooK = null;
lblResultMessage.Text = "Extract for Genpact has been successfully generated";
lblResultMessage.Visible = true;
}
As the column K is the one containing the wrong dates, I tried to format this column to get my dates well formatted, but without success.
Any idea on what's possibly going wrong ?
I think the issue is you are leaving too much to Excel and need to be explicit in how you enter and render the data.
My recommendation:
Read the data from the database as a date, not a string
Enter it into Excel as a date, as Excel interprets dates
Format the cell the way you want to see it
Example:
Excel.Range r = worksheet.Cells[rowcount - 1, i];
if (datarow[i - 1] is DateTime)
{
r.Value2 = ((DateTime)(datarow[i - 1])).ToOADate();
r.NumberFormat = "dd/MM/yyyy";
}
else
{
r.Value2 = datarow[i - 1]
}
Bear in mind you can skip the datatable all together and just use an OracleDataReader to read the data and then put it directly into Excel.
Further, if you have an ODBC connection, you can have Excel read it directly from Oracle and put it into a Table -- it will even handle the dates properly if you do it that way. You can still do this from C#, by the way, with a few lines of code, and you let Excel do the heavy lifting.
Related
I'm copying data from first sheet of different excel files to a single workbook. I already have tried it with different alternatives like npoi, spire.xls and Interop which works good, but it kills too much of my time. It would really be thankful if anyone can suggest me with a better one. Been through many forms on the web, but couldn't find.
FYI: Each of My files are more than 50 MB in size. A few being 10 MB or less.
This is one of which I have tried (Uses Spire.xls):
workbook = new Workbook();
//laod first file
workbook.LoadFromFile(names[0]);
//load the remaining files starting with second file
for (int i = 1; i < cnt; i++)
{
LoadFIle(names[i]);
//merge the loaded file immediately and than load next file
MergeData();
}
private void LoadFIle(string filePath)
{
//load other workbooks starting with 2nd workbbook
tempbook = new Workbook();
tempbook.LoadFromFile(filePath);
}
private void MergeData()
{
try
{
int c1 = workbook.ActiveSheet.LastRow, c2 = tempbook.Worksheets[0].LastRow;
//check if you have exceeded 1st sheet limit
if ((c1 + c2) <= 1048575)
{
//import the second workbook's worksheet into the first workbook using a datatable
//load 1st sheet of tempbook into sheet
Worksheet sheet = tempbook.Worksheets[0];
//copy data from sheet into a datatable
DataTable dataTable = sheet.ExportDataTable();
//load sheet1
Worksheet sheet1 = workbook.Worksheets[workbook.ActiveSheetIndex];
sheet1.InsertDataTable(dataTable, false, sheet1.LastRow + 1, 1);
}
else if ((c1 >= 1048575 && c2 >= 1048575) || c1 >= 1048575 || c2 >= 1048575 || (c1 + c2) >= 1048575)
{
workbook.Worksheets.AddCopy(tempbook.Worksheets[0]);
indx = workbook.ActiveSheet.Index;
workbook.ActiveSheetIndex = ++indx;
}
else
{
//import the second workbook's worksheet into the first workbook using a datatable
//load 1st sheet of tempbook into sheet
Worksheet sheet = tempbook.Worksheets[0];
//copy data from sheet into a datatable
DataTable dataTable = sheet.ExportDataTable();
//load sheet1
Worksheet sheet1 = workbook.Worksheets[workbook.ActiveSheetIndex];
sheet1.InsertDataTable(dataTable, false, sheet1.LastRow + 1, 1);
}
}
catch (IndexOutOfRangeException)
{
}
}
}
Well, this works good but as said takes a long time. Any suggestions are welcome. Thanks in advance.
Here is my (fastest I know of) implementation using Excel interop. Although I looked carefully to release all (must have missed one), 2 Excel instances remain in the processes list, they are closed after the program ends.
The key is to only have 2 Open Excel instances and to copy the data as a Block using Range.Value2.
//Helper function to cleanup
public void ReleaseObject(object obj)
{
if (obj != null && Marshal.IsComObject(obj))
{
Marshal.ReleaseComObject(obj);
}
}
public void CopyIntoOne(List<string> pSourceFiles, string pDestinationFile)
{
var sourceExcelApp = new Microsoft.Office.Interop.Excel.Application();
var destinationExcelApp = new Microsoft.Office.Interop.Excel.Application();
// TODO: Check if it exists
destinationExcelApp.Workbooks.Open(pDestinationFile);
// for debug
//destinationExcelApp.Visible = true;
//sourceExcelApp.Visible = true;
int i = 0;
var sheets = destinationExcelApp.ActiveWorkbook.Sheets;
var lastsheet = destinationExcelApp.ActiveWorkbook.Sheets[sheets.Count];
ReleaseObject(sheets);
foreach (var srcFile in pSourceFiles)
{
sourceExcelApp.Workbooks.Open(srcFile);
// get extends
var lastRow = sourceExcelApp.ActiveSheet.Cells.Find("*", System.Reflection.Missing.Value,
System.Reflection.Missing.Value, System.Reflection.Missing.Value, XlSearchOrder.xlByRows,
XlSearchDirection.xlPrevious, false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
var lastCol = sourceExcelApp.ActiveSheet.Cells.Find("*", System.Reflection.Missing.Value, System.Reflection.Missing.Value,
System.Reflection.Missing.Value, XlSearchOrder.xlByColumns, XlSearchDirection.xlPrevious, false,
System.Reflection.Missing.Value, System.Reflection.Missing.Value);
var startCell = (Range) sourceExcelApp.ActiveWorkbook.ActiveSheet.Cells[1, 1];
var endCell = (Range) sourceExcelApp.ActiveWorkbook.ActiveSheet.Cells[lastRow.Row, lastCol.Column];
var myRange = sourceExcelApp.ActiveWorkbook.ActiveSheet.Range[startCell, endCell];
// copy the values
var value = myRange.Value2;
// create sheet in new Workbook at the end
Worksheet newSheet = destinationExcelApp.ActiveWorkbook.Sheets.Add(After: lastsheet);
ReleaseObject(lastsheet);
lastsheet = newSheet;
//its even faster when adding it at the front
//Worksheet newSheet = destinationExcelApp.ActiveWorkbook.Sheets.Add();
// change that to a good name
newSheet.Name = ++i + "";
var dstStartCell = (Range) destinationExcelApp.ActiveWorkbook.ActiveSheet.Cells[1, 1];
var dstEndCell = (Range) destinationExcelApp.ActiveWorkbook.ActiveSheet.Cells[lastRow.Row, lastCol.Column];
var dstRange = destinationExcelApp.ActiveWorkbook.ActiveSheet.Range[dstStartCell, dstEndCell];
// this is the actual paste
dstRange.Value2 = value;
//cleanup
ReleaseObject(startCell);
ReleaseObject(endCell);
ReleaseObject(myRange);
ReleaseObject(value);// cannot hurt, but not necessary since its a simple array
ReleaseObject(dstStartCell);
ReleaseObject(dstEndCell);
ReleaseObject(dstRange);
ReleaseObject(newSheet);
ReleaseObject(lastRow);
ReleaseObject(lastCol);
sourceExcelApp.ActiveWorkbook.Close(false);
}
ReleaseObject(lastsheet);
sourceExcelApp.Quit();
ReleaseObject(sourceExcelApp);
destinationExcelApp.ActiveWorkbook.Save();
destinationExcelApp.Quit();
ReleaseObject(destinationExcelApp);
destinationExcelApp = null;
sourceExcelApp = null;
}
I have tested it on small excel files and are curious how it behaves with larger files.
I have a FindRange that finds rows with the text `€´(lblValutaTeken.Text) in my Excel sheet.
What I need to do is get the rows(multiple!) that contain the text and copy them to clipboard. My range is not consecutive rows, which is the basis of my despair.
So far I got this
object[,] cellValues = null;
try
{
Excel.Range currentFind = null;
Excel.Range firstFind = null;
var missing = Missing.Value;
Excel.Range RangeWithValutaSigns = xlApp.ActiveSheet.Range("g1", "g500");
currentFind = RangeWithValutaSigns.Find(lblValutaTeken.Text, missing,
Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart,
Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, false,
missing, missing);
while (currentFind != null)
{
if (firstFind == null)
{
firstFind = currentFind;
}
else if (currentFind.get_Address(Excel.XlReferenceStyle.xlA1)
== firstFind.get_Address(Excel.XlReferenceStyle.xlA1))
{
break;
}
Console.WriteLine("~~~~ currentFind.Row = " + currentFind.Row);
Excel.Range currentFindRow = currentFind.Range[("B" + currentFind.Row), ("H" + currentFind.Row)];
cellValues = (object[,])currentFindRow.Value2;
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(cellValues))
{
string name = descriptor.Name;
object value = descriptor.GetValue(cellValues);
Console.WriteLine("{0}={1}", name, value);
}
for (int j = 1; j < 8; j++)
{
Console.WriteLine(cellValues[i, j].ToString());
}
i = i + 1;
currentFind = RangeWithValutaSigns.FindNext(currentFind);
}
}
catch (Exception ex)
{
MessageBox.Show("Er is een fout gemaakt tijdens het kopiëren van de productregels uit de offerte naar het clipboard." + System.Environment.NewLine + System.Environment.NewLine + "Controleer of er een excel geopend is met daarin regels met productcodes uit de LookApp." + System.Environment.NewLine + System.Environment.NewLine + "Error message:" + System.Environment.NewLine + ex.Message);
}
Clipboard.SetDataObject(cellValues);
I don't know if this will help you or not, but here is my idea:
If you are searching for rows with some text criteria, why not create a string array with that criteria, and then filter your excel file based on your criteria, afterwards, copying those rows.
Example :
string[] Criteria = new string[1]
Criteria[0] = "Your Criteria"; (if the criteria can vary you can use * )
Criteria[1] = "Some other Criteria";
And then filter your excel sheet based on a criteria:
Create a range:
Xl.Range myRange = yourSheet.UsedRange;
next filter :
myRange.AutoFilter(7, Criteria[0], xl.XlAutoFilterOperator.xlOr, Criteria[1], true);
xl.Range my_Range= myRange.SpecialCells(xl.XlCellType.xlCellTypeVisible, Type.Missing);
And now all you need to do is select and copy your remaining visible rows and paste them.
Hopefully this will be of some help.
So, with putting answers from multiple other Q&A's together I came up with this. Not very elegant, but it does precisely what I wanted it to.
Step 1. Create a datatable
Step 2. Use Excel.Range.Find to find the ranges(=cells) that have my valutasign (is same as lblvalutasign.txt, for instance € or $ or CAD)
Step 3. Each time a range is found I use Range.Offset to put cellvalues into my datable in the corresponding column.
Step 4. Put the whole datatable into a datagridview. This can be a non-visible datagridview if you want. Lay-out the datagridview the way you want to.
Step 5. DataGridView.SelectAll() and copy to clipboard with
DataObject d = dataGridView1.GetClipboardContent();
Clipboard.SetDataObject(d);
Step 6. Use ctr+v to paste anywhere!
Excel.Application xlApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
Excel.Range myRange;
myRange = xlApp.ActiveSheet.UsedRange;
DataTable dtProductRowsFromExcel = new DataTable(); //to save all productrows form excel offer.
dtProductRowsFromExcel.Columns.Add("Code", typeof(String)); //column 1 Excel B
dtProductRowsFromExcel.Columns.Add("Amount", typeof(String)); //column 2 Excel C
dtProductRowsFromExcel.Columns.Add("Unit", typeof(String)); //column 3 Excel D
dtProductRowsFromExcel.Columns.Add("ValutaUnit", typeof(String)); //column 4 Excel E
dtProductRowsFromExcel.Columns.Add("PriceUnit", typeof(String)); //column 5 Excel F
dtProductRowsFromExcel.Columns.Add("ValutaTotal", typeof(String)); //column 6 Excel G
dtProductRowsFromExcel.Columns.Add("PriceTotal", typeof(String)); //column 7 Excel H
try
{
Excel.Range currentFind = null;
Excel.Range firstFind = null;
var missing = Missing.Value;
Excel.Range RangeWithValutaSigns = xlApp.ActiveSheet.Range("g1", "g500");
currentFind = RangeWithValutaSigns.Find(lblValutaTeken.Text, missing,
Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart,
Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, false,
missing, missing);
while (currentFind != null)
{
if (firstFind == null)
{
firstFind = currentFind;
}
else if (currentFind.get_Address(Excel.XlReferenceStyle.xlA1)
== firstFind.get_Address(Excel.XlReferenceStyle.xlA1))
{
break;
}
Console.WriteLine("~~~~ currentFind.Row = " + currentFind.Row);
string B = currentFind.Offset[0, -5].Value2.ToString();
string C = (currentFind.Offset[0, -4].Value2 != null) ? currentFind.Offset[0, -4].Value2.ToString() : "";
string D = (currentFind.Offset[0, -3].Value2 != null) ? currentFind.Offset[0, -3].Value2.ToString() : "";
string E = (currentFind.Offset[0, -2].Value2 != null) ? currentFind.Offset[0, -2].Value2.ToString() : "";
string F = (currentFind.Offset[0, -1].Value2 != null) ? currentFind.Offset[0, -1].Value2.ToString() : "";
string G = currentFind.Value2.ToString();
string H = (currentFind.Offset[0, 1].Value2 != null) ? currentFind.Offset[0, 1].Value2.ToString() : "";
dtProductRowsFromExcel.Rows.Add(B, C, D, E, F, G, H);
currentFind = RangeWithValutaSigns.FindNext(currentFind);
}
// to check datatable in output window use:
foreach (DataRow dataRow in dtProductRowsFromExcel.Rows)
{
foreach (var item in dataRow.ItemArray)
{
Console.WriteLine(item);
}
}
dataGridView1.ReadOnly = true;
dataGridView1.RowHeadersVisible = false;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
dataGridView1.DataSource = dtProductRowsFromExcel;
dataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
dataGridView1.SelectAll();
DataObject d = dataGridView1.GetClipboardContent();
Clipboard.SetDataObject(d);
}
catch (Exception ex)
{
MessageBox.Show("Error message:" + System.Environment.NewLine + ex.Message);
}
Im using Visual Studio to create an automated test that creates two excel sheets. As a final check, I need to compare the content of these two excel sheets and ensure that they are equal. Is there any way to do this with assertions?
Something like Assert.AreEqual(file1, file2);?
Any help or guidance would be appreciated!
Thanks to Mangist for guidance on this. Ive written the following to compare two excel files:
public bool compareFiles(string filePath1, string filePath2)
{
bool result = false;
Excel.Application excel = new Excel.Application();
//Open files to compare
Excel.Workbook workbook1 = excel.Workbooks.Open(filePath1);
Excel.Workbook workbook2 = excel.Workbooks.Open(filePath2);
//Open sheets to grab values from
Excel.Worksheet worksheet1 = (Excel.Worksheet)workbook1.Sheets[1];
Excel.Worksheet worksheet2 = (Excel.Worksheet)workbook2.Sheets[1];
//Get the used range of cells
Excel.Range range = worksheet2.UsedRange;
int maxColumns = range.Columns.Count;
int maxRows = range.Rows.Count;
//Check that each cell matches
for (int i = 1; i <= maxColumns; i++)
{
for (int j = 1; j <= maxRows; j++)
{
if (worksheet1.Cells[j, i].Value == worksheet2.Cells[j, i].Value)
{
result = true;
}
else
result = false;
}
}
//Close the workbooks
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.ReleaseComObject(range);
Marshal.ReleaseComObject(worksheet1);
Marshal.ReleaseComObject(worksheet2);
workbook1.Close();
workbook2.Close();
excel.Quit();
Marshal.ReleaseComObject(excel);
//Tell us if it is true or false
return result;
}
And using an assertion to check result:
Assert.IsTrue(compareFiles(testFile, compareFile), "Output files do not match.");
Can you convert the expected/actual Excel sheets to a text format, such as CSV?
If so, you could use Approval Tests instead. This allows you to have a text file as your "expected" test result. When tests fail it can show you the actual result of the test, diff'd against the expected result.
Screenshot taken from this review of Approval Tests.
One Option will be using Open source library called as EPPlus. You can download and refer it in you automated test application. EPPlus gives you multiple ways to read an excel file and compare. One such option is C# Datatable. Here is a sample code..
public static DataTable GetDataTableFromExcel(string path, bool hasHeader = true)
{
using (var pck = new OfficeOpenXml.ExcelPackage())
{
using (var stream = File.OpenRead(path))
{
pck.Load(stream);
}
var ws = pck.Workbook.Worksheets.First();
DataTable tbl = new DataTable();
foreach (var firstRowCell in ws.Cells[1, 1, 1, ws.Dimension.End.Column])
{
tbl.Columns.Add(hasHeader ? firstRowCell.Text : string.Format("Column {0}", firstRowCell.Start.Column));
}
var startRow = hasHeader ? 2 : 1;
for (int rowNum = startRow; rowNum <= ws.Dimension.End.Row; rowNum++)
{
var wsRow = ws.Cells[rowNum, 1, rowNum, ws.Dimension.End.Column];
DataRow row = tbl.Rows.Add();
foreach (var cell in wsRow)
{
row[cell.Start.Column - 1] = cell.Text;
}
}
return tbl;
}
}
For both the Excel files , same process can be adopted, and it will give you the desired results.
I am exporting three worked sheet in single XL file, but I am missing some user data in the second DataTable (Education Details sheet) and third DataTable (Employeement Details sheet).
The Education Details sheet is some users are not there, but an Employeement Details sheet that users are showing. User Email Id's is there all three Database Tables.
DataSe ds = new DataSet();
DataTable dt = new DataTable("Registration Details");
DataTable dt1 = new DataTable("Education Details");
DataTable dt2 = new DataTable("Employeement Details");
dt = bl.Get_Registrationdetailsbydate(bo);
gv_Regdetails.DataSource = dt;
gv_Regdetails.DataBind();
dt1 = bl.Get_Registrationdetailsbydate1(bo);
dt2 = bl.Get_Registrationdetailsbydate2(bo);
DataTable filteredEducation = dt1.AsEnumerable()
.Where(x => dt.AsEnumerable()
.Any(z => z.Field<string>("Email").Trim() == x.Field<string>("Email").Trim()))
.CopyToDataTable();
DataTable filteredEmployee = dt2.AsEnumerable()
.Where(x => dt.AsEnumerable()
.Any(z => z.Field<string>("Email").Trim() == x.Field<string>("Email").Trim()))
.CopyToDataTable();
dt.TableName = "Registration Details";
filteredEducation.TableName = "Education Details";
filteredEmployee.TableName = "Employeement Details";
ds.Tables.Add(dt);
ds.Tables.Add(filteredEducation);
ds.Tables.Add(filteredEmployee);
ExcelHelper.ToExcel(ds, "DangoteUsers.xls", Page.Response);
I did result base on first DataTable users Email, then fill second DataTable detail users base on first DataTable Email id's. Same as Employment Details. The issue in first DataTable and second DataTable. I am not returning the DataTable also.
I refer this example
The problem is coming somewhere from the solution of conversion from DataSet to Excel in the article. Using this self made conversion is not a good idea. Use Jet/ACE engine or Microsoft Office Interop. At least they guarantee, they don't have such kind of bugs, which in future can became more. Better use something which is already highly accepted by the community. Here I wrote an approach how to do it with Interop.
First what you need to do is to add the reference to Microsoft.Office.Interop.Excel. Here is how to do it, taken from msdn article
Add the Excel assembly as a reference to the project: Right-click on
the project, select Add Reference.
Click the COM tab of the Add Reference dialog box, and find Microsoft
Excel 11 Object Library.
Double-click on Microsoft Excel 11 Object Library, and
press OK.
Obviously if you have bigger version of Excel 11 use it.
Here is the code, there are comments/regions with the workflow of it. You should use using Excel = Microsoft.Office.Interop.Excel; as reference
public void ExcelBtn_Click(object sender, EventArgs e)
{
DataSet dst = PrepareData();
byte[] bytes = ExportDataSetToExcel(dst);
Response.ClearContent();
Response.ContentType = "application/msoffice";
Response.AddHeader("Content-Disposition", #"attachment; filename=""ExportedExcel.xlsx"" ");
Response.BinaryWrite(bytes);
Response.End();
}
public static DataSet PrepareData()
{
DataTable badBoysDst = new DataTable("BadBoys");
badBoysDst.Columns.Add("Nr");
badBoysDst.Columns.Add("Name");
badBoysDst.Rows.Add(1, "Me");
badBoysDst.Rows.Add(2, "You");
badBoysDst.Rows.Add(3, "Pepe");
badBoysDst.Rows.Add(4, "Roni");
//Create a Department Table
DataTable goodBoysDst = new DataTable("GoodBoys");
goodBoysDst.Columns.Add("Nr");
goodBoysDst.Columns.Add("Name");
goodBoysDst.Rows.Add("1", "Not me");
goodBoysDst.Rows.Add("2", "Not you");
goodBoysDst.Rows.Add("3", "Quattro");
goodBoysDst.Rows.Add("4", "Stagioni");
DataTable goodBoysDst2 = new DataTable("GoodBoys2");
goodBoysDst2.Columns.Add("Nr");
goodBoysDst2.Columns.Add("Name");
goodBoysDst2.Rows.Add("1", "Not me");
goodBoysDst2.Rows.Add("2", "Not you");
goodBoysDst2.Rows.Add("3", "Quattro");
goodBoysDst2.Rows.Add("4", "Stagioni");
DataTable goodBoysDst3 = new DataTable("GoodBoys3");
goodBoysDst3.Columns.Add("Nr");
goodBoysDst3.Columns.Add("Name");
goodBoysDst3.Rows.Add("1", "Not me");
goodBoysDst3.Rows.Add("2", "Not you");
goodBoysDst3.Rows.Add("3", "Quattro");
goodBoysDst3.Rows.Add("4", "Stagioni");
//Create a DataSet with the existing DataTables
DataSet dst = new DataSet("SchoolBoys");
dst.Tables.Add(badBoysDst);
dst.Tables.Add(goodBoysDst);
dst.Tables.Add(goodBoysDst2);
dst.Tables.Add(goodBoysDst3);
return dst;
}
public static byte[] ExportDataSetToExcel(DataSet dst)
{
#region Create The Excel
Excel.Application excelApp = null;
Excel.Workbook excelWorkBook = null;
try
{
excelApp = new Excel.Application();
if (excelApp == null)
throw new Exception("You can throw custom exception here too");
excelWorkBook = excelApp.Workbooks.Add();
int sheetNr = 1;
foreach (DataTable table in dst.Tables)
{
Excel.Worksheet excelWorkSheet = null;
//Add a new worksheet or reuse first 3 sheets of workbook with the Datatable name
if (sheetNr <= excelWorkBook.Sheets.Count)
{
excelWorkSheet = excelWorkBook.Sheets.get_Item(sheetNr);
}
else
{
excelWorkSheet = excelWorkBook.Sheets.Add(After: excelWorkBook.Sheets[excelWorkBook.Sheets.Count]);
}
excelWorkSheet.Name = table.TableName;
for (int i = 1; i < table.Columns.Count + 1; i++)
{
excelWorkSheet.Cells[1, i] = table.Columns[i - 1].ColumnName;
}
for (int j = 0; j < table.Rows.Count; j++)
{
for (int k = 0; k < table.Columns.Count; k++)
{
excelWorkSheet.Cells[j + 2, k + 1] = table.Rows[j].ItemArray[k].ToString();
}
}
sheetNr += 1;
}
//make first sheet active
excelApp.ActiveWorkbook.Sheets[1].Select();
excelWorkBook.SaveAs(#"c:\temp\DataSetToExcel.xlsx");
}
finally
{
excelWorkBook.Close();
excelApp.Quit();
//you should call GC here because there is memory problem with Interop
GC.Collect();
GC.WaitForPendingFinalizers();
}
#endregion
#region Take byte[] of the excel
byte[] result = null;
using (FileStream fs = new FileStream(#"c:\temp\DataSetToExcel.xlsx", FileMode.Open, FileAccess.Read))
{
BinaryReader reader = new BinaryReader(fs);
result = reader.ReadBytes((int)fs.Length);
}
#endregion
#region Delete the excel from the server
File.Delete(#"c:\temp\DataSetToExcel.xlsx");
#endregion
return result;
}
}
So try to use something established by the community already.This is pretty much full example how to do it with Interop. Personally I prefer to use ACE/JET engines, because there is no memory leaks problems like in the Interop(because of that we are calling the GC in the code). Creation of new sheets with ACE/JET engine is a little bit harder.
I think your string comparison in linq query is a problem..your email address might have different case which could have caused this issue. Try below code
DataTable filteredEducation = dt1.AsEnumerable()
.Where(x => dt.AsEnumerable()
.Any(z => z.Field<string>("Email").Trim().Equals(x.Field<string>("Email").Trim(),StringComparison.CurrentCultureIgnoreCase)))
.CopyToDataTable();
DataTable filteredEmployee = dt2.AsEnumerable()
.Where(x => dt.AsEnumerable()
.Any(z => z.Field<string>("Email").Trim().Equals(x.Field<string>("Email").Trim(),StringComparison.CurrentCultureIgnoreCase)))
.CopyToDataTable();
I had done the same export problem by manual export. First, i need to prepare a responce http responce properly, than add all headers(with rowsapn and colspan attributes) of your tables and then populate data:
//this fun is called after click on export button for example
public void Export(string fileName, GridView gv)
{
try
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}", String.Format("{0}.xls", fileName)));
HttpContext.Current.Response.AddHeader("Content-Transfer-Encoding", "utf-8");
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.Write(#"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">");
HttpContext.Current.Response.Charset = "utf-8";//"windows-1251";//
HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.GetEncoding("utf-8");
using (StringWriter sw = new StringWriter())
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
// Create a table to contain the grid
Table table = new Table();
table.Width = Unit.Percentage(100);
// include the gridline settings
table.GridLines = gv.GridLines;
//header
TableRow r = new TableRow();
TableCell cell = new TableCell()
{
ColumnSpan = 18,
Text = fileName,
BackColor = Color.LightGray,
HorizontalAlign = HorizontalAlign.Center
};
cell.Font.Size = new FontUnit(14);
r.Cells.Add(cell);
table.Rows.Add(r);
GridViewRow row;
int rowSpan = 0;
//second row
row = CreateSecondHeaderRow();
table.Rows.AddAt(1, row);
//first row
row = CreateFirstHeaderRow(row, rowSpan);
table.Rows.AddAt(1, row);
// add each of the data rows to the table
for (int j = 0; j < gv.Rows.Count; j++)
{
//Set the default color
gv.Rows[j].BackColor = System.Drawing.Color.White;
for (int i = 0; i < gv.Rows[j].Cells.Count; i++)
{
gv.Rows[j].Cells[i].BackColor = System.Drawing.Color.White;
gv.Rows[j].Cells[i].Width = gv.Columns[i].ItemStyle.Width;
gv.Rows[j].Cells[i].Font.Size = gv.Columns[i].ItemStyle.Font.Size;
gv.Rows[j].Cells[i].Font.Bold = gv.Columns[i].ItemStyle.Font.Bold;
gv.Rows[j].Cells[i].Font.Italic = gv.Columns[i].ItemStyle.Font.Italic;
//aligh
if (i == 0)
{
gv.Rows[j].Cells[i].Style["text-align"] = "center";
}
else
{
gv.Rows[j].Cells[i].Style["text-align"] = "right";
}
//for alternate
if (j % 2 != 1) gv.Rows[j].Cells[i].BackColor = Color.LightSteelBlue;
}
table.Rows.Add(gv.Rows[j]);
}
table.RenderControl(htw);
// render the htmlwriter into the response
HttpContext.Current.Response.Write(sw);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
}
}
}
catch (Exception ex)
{
this._hasError = true;
ShowError(ex);
}
}
private TableHeaderCell CreateHeaderCell(string text = null, int rowSpan = 0, int columnSpan = 0, Color backColor = default(Color), Color foreColor = default(Color))
{
if (object.Equals(backColor, default(Color))) backColor = Color.LightGray;
if (object.Equals(foreColor, default(Color))) foreColor = Color.Black;
return new TableHeaderCell
{
RowSpan = rowSpan,
ColumnSpan = columnSpan,
Text = text,
BackColor = backColor
};
}
private GridViewRow CreateFirstHeaderRow(GridViewRow row, int rowSpan)
{
row = new GridViewRow(0, 0, DataControlRowType.Header, DataControlRowState.Insert);
TableHeaderCell cell = CreateHeaderCell("Surplus %");
row.Controls.Add(cell);
cell = CreateHeaderCell("The date", columnSpan: 2);
row.Controls.Add(cell);
if (this.WithQuantity)
{
cell = CreateHeaderCell("Total Quantity", 2 + rowSpan, backColor: Color.Yellow);
row.Controls.Add(cell);
}
cell = CreateHeaderCell("Total Amount", 2 + rowSpan);
row.Controls.Add(cell);
cell = CreateHeaderCell("Has elapsed periods from start", columnSpan: (this.WithQuantity ? (SurplusUtil.TheColumnsNumbers * 2) : SurplusUtil.TheColumnsNumbers));
row.Controls.Add(cell);
if (this.WithQuantity)
{
cell = CreateHeaderCell("Quantity <br style='mso-data-placement:same-cell;' /> surplus", 2 + rowSpan, backColor: Color.Yellow);
row.Controls.Add(cell);
}
cell = CreateHeaderCell("Principal <br style='mso-data-placement:same-cell;' /> surplus", 2 + rowSpan);
row.Controls.Add(cell);
return row;
}
private GridViewRow CreateSecondHeaderRow()
{
GridViewRow row = new GridViewRow(0, 0, DataControlRowType.Header, DataControlRowState.Insert);
TableHeaderCell cell = CreateHeaderCell("Period number", rowSpan: ((this.WithQuantity) ? 2 : 0));
row.Controls.Add(cell);
cell = CreateHeaderCell("from", rowSpan: ((this.WithQuantity) ? 2 : 0));
row.Controls.Add(cell);
cell = CreateHeaderCell("to", rowSpan: ((this.WithQuantity) ? 2 : 0));
row.Controls.Add(cell);
for (int i = 0; i < SurplusUtil.TheColumnsNumbers; i++)
{
cell = CreateHeaderCell(i.ToString(),
columnSpan: ((this.WithQuantity) ? 2 : 0),
backColor: System.Drawing.Color.FromArgb(198, 239, 206),
foreColor: System.Drawing.Color.FromArgb(0, 97, 0));
row.Controls.Add(cell);
}
return row;
}
I used this code snippet to export data to Excel using Open XML.
The code was in VB.Net so I converted it to C#. It's working fine with only one (annoying) glitch. The last cell (Z5) is blank. When I used the VB.Net version it's filling all the cells. I manually compared the C# vs the VB.Net code but it's functionally similar byte by byte. But still it's not able to fill up the last cell. Any idea why?
C# version of the code is below for your reference:
protected void Export()
{
Response.ClearHeaders();
Response.ClearContent();
Response.Clear();
Response.Buffer = true;
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
//"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" '"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" '"application/vnd.ms-excel"
Response.AddHeader("content-disposition", "attachment; filename=Test.xlsx");
Response.Charset = "";
this.EnableViewState = false;
MemoryStream ms = new MemoryStream();
SpreadsheetDocument objSpreadsheet = SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook);
WorkbookPart objWorkbookPart = objSpreadsheet.AddWorkbookPart();
objWorkbookPart.Workbook = new Workbook();
WorksheetPart objSheetPart = objWorkbookPart.AddNewPart<WorksheetPart>();
objSheetPart.Worksheet = new Worksheet(new SheetData());
Sheets objSheets = objSpreadsheet.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
Sheet objSheet = new Sheet();
objSheet.Id = objSpreadsheet.WorkbookPart.GetIdOfPart(objSheetPart);
objSheet.SheetId = 1;
objSheet.Name = "mySheet";
objSheets.Append(objSheet);
for (int intRow = (int)('A'); intRow <= (int)('Z'); intRow++)
{
for (uint intCol = 1; intCol <= 5; intCol++)
{
Cell objCell = InsertCellInWorksheet(Convert.ToString((char)intRow), intCol, objSheetPart);
objCell.CellValue = new CellValue("This was a test: " + Convert.ToString((char)intRow) + intCol.ToString());
objCell.DataType = new EnumValue<CellValues>(CellValues.String);
objSpreadsheet.WorkbookPart.Workbook.Save();
}
}
objSpreadsheet.WorkbookPart.Workbook.Save();
objSpreadsheet.Close();
ms.WriteTo(Response.OutputStream);
Response.Flush();
Response.End();
}
private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
var sheetData = worksheet.GetFirstChild<SheetData>();
string cellReference = columnName + rowIndex;
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
if (sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)
{
row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
}
else
{
row = new Row { RowIndex = rowIndex };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference.Value == columnName + rowIndex).Count() > 0)
{
return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
}
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell refCell = row.Elements<Cell>().FirstOrDefault(cell => string.Compare(cell.CellReference.Value, cellReference, true) > 0);
var newCell = new Cell { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
worksheet.Save();
return newCell;
}
Issue is resolved. I made a small change to the code to make it work. Instead of saving the sheet inside InsertCellInWorksheet method. I am saving the sheet outside the for loops. Here is the working version of the code.
protected void Export()
{
Response.ClearHeaders();
Response.ClearContent();
Response.Clear();
Response.Buffer = true;
Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
//"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" '"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" '"application/vnd.ms-excel"
Response.AddHeader("content-disposition", "attachment; filename=Test.xlsx");
Response.Charset = "";
this.EnableViewState = false;
MemoryStream ms = new MemoryStream();
SpreadsheetDocument objSpreadsheet = SpreadsheetDocument.Create(ms, SpreadsheetDocumentType.Workbook);
WorkbookPart objWorkbookPart = objSpreadsheet.AddWorkbookPart();
objWorkbookPart.Workbook = new Workbook();
WorksheetPart objSheetPart = objWorkbookPart.AddNewPart<WorksheetPart>();
objSheetPart.Worksheet = new Worksheet(new SheetData());
Sheets objSheets = objSpreadsheet.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
Sheet objSheet = new Sheet();
objSheet.Id = objSpreadsheet.WorkbookPart.GetIdOfPart(objSheetPart);
objSheet.SheetId = 1;
objSheet.Name = "mySheet";
objSheets.Append(objSheet);
for (int intRow = (int)('A'); intRow <= (int)('Z'); intRow++)
{
for (uint intCol = 1; intCol <= 5; intCol++)
{
Cell objCell = InsertCellInWorksheet(Convert.ToString((char)intRow), intCol, objSheetPart);
objCell.CellValue = new CellValue("This was a test: " + Convert.ToString((char)intRow) + intCol.ToString());
objCell.DataType = new EnumValue<CellValues>(CellValues.String);
}
}
objSheetPart.Worksheet.Save();
objSpreadsheet.WorkbookPart.Workbook.Save();
objSpreadsheet.Close();
ms.WriteTo(Response.OutputStream);
Response.Flush();
Response.End();
}
private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
var sheetData = worksheet.GetFirstChild<SheetData>();
string cellReference = columnName + rowIndex;
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
if (sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)
{
row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
}
else
{
row = new Row { RowIndex = rowIndex };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference.Value == columnName + rowIndex).Count() > 0)
{
return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
}
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell refCell = row.Elements<Cell>().FirstOrDefault(cell => string.Compare(cell.CellReference.Value, cellReference, true) > 0);
var newCell = new Cell { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
//worksheet.Save();
return newCell;
}
Not sure how it worked in VB.Net :-)
The loop through the rows and columns that you have doesn't look right. It looks like you are looping from row = 65 (i.e. (int)('A') to row = 65 + 26 (i.e. (int)('Z')) and then col = 1 to 3.
Maybe you should loop from col = 1 to 5.
I think you possibly need to have a look at the xml you are dealing with and make sure you are moving through the document (rows and columns) correctly. If you weren't aware of it all office 2007 and later documents are openXml document. They are basically zip files that contain xml. If you take your spreadsheet and change the extension from .xlsx to .zip you will be able to open the document as a zip file and browse the contents. I haven't worked with openXml for over a year, but if my memory serves me correctly you will find the xml representing the sheets in the sheets folder.