I'm trying to use a Script Task to export data to Excel because some of the reports I generate simply have too many columns to keep using a template file.
The most annoying part about using a template is: if something as simple as a column header changes, the metadata gets screwed forcing me to recreate my DataFlow. Because I use an OLE DB source, I need to use a Data Transformation task to convert between unicode and non-unicode character sets, then remap my Excel Destination to the "Copy of field x" in order for the Excel document to create properly.
This takes far too long and I need a new approach.
I have the following method in a script task using Excel = Microsoft.Office.Interop.Excel:
private void ExportToExcel(DataTable dataTable, string excelFilePath = null)
{
Excel.Application excelApp = new Excel.Application();
Excel.Worksheet workSheet = null;
try
{
if (dataTable == null || dataTable.Columns.Count == 0)
throw new System.Exception("Null or empty input table!" + Environment.NewLine);
excelApp.Workbooks.Add();
workSheet = excelApp.ActiveSheet;
for (int i = 0; i < dataTable.Columns.Count; i++)
{
workSheet.Cells[1, (i + 1)] = dataTable.Columns[i].ColumnName;
}
foreach (DataTable dt in dataSet.Tables)
{
// Copy the DataTable to an object array
object[,] rawData = new object[dt.Rows.Count + 1, dt.Columns.Count];
// Copy the column names to the first row of the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
rawData[0, col] = dt.Columns[col].ColumnName;
}
// Copy the values to the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
for (int row = 0; row < dt.Rows.Count; row++)
{
rawData[row + 1, col] = dt.Rows[row].ItemArray[col];
}
}
// Calculate the final column letter
string finalColLetter = string.Empty;
string colCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int colCharsetLen = colCharset.Length;
if (dt.Columns.Count > colCharsetLen)
{
finalColLetter = colCharset.Substring((dt.Columns.Count - 1) / colCharsetLen - 1, 1);
}
finalColLetter += colCharset.Substring((dt.Columns.Count - 1) % colCharsetLen, 1);
workSheet.Name = dt.TableName;
// Fast data export to Excel
string excelRange = string.Format("A1:{0}{1}", finalColLetter, dt.Rows.Count + 1);
//The code crashes here (ONLY in SSIS):
workSheet.get_Range(excelRange, Type.Missing).Value2 = rawData;
// Mark the first row as BOLD
((Excel.Range)workSheet.Rows[1, Type.Missing]).Font.Bold = true;
}
List<int> lstColumnsToSum = new List<int>() { 9 };
Dictionary<int, string> dictColSumName = new Dictionary<int, string>() { { 9, "" } };
Dictionary<int, decimal> dictColumnSummation = new Dictionary<int, decimal>() { { 9, 0 } };
// rows
for (int i = 0; i < dataTable.Rows.Count; i++)
{
for (int j = 1; j <= dataTable.Columns.Count; j++)
{
workSheet.Cells[(i + 2), (j)] = dataTable.Rows[i][j - 1];
if (lstColumnsToSum.Exists(x => (x == j)))
{
decimal val = 0;
if (decimal.TryParse(dataTable.Rows[i][j - 1].ToString(), out val))
{
dictColumnSummation[j] += val;
}
}
}
}
//Footer
int footerRowIdx = 2 + dataTable.Rows.Count;
foreach (var summablecolumn in dictColSumName)
{
workSheet.Cells[footerRowIdx, summablecolumn.Key] = String.Format("{0}", dictColumnSummation[summablecolumn.Key]);
}
// check fielpath
if (excelFilePath != null && excelFilePath != "")
{
try
{
if (File.Exists(excelFilePath))
File.Delete(excelFilePath);
workSheet.Activate();
workSheet.Application.ActiveWindow.SplitRow = 1;
workSheet.Application.ActiveWindow.FreezePanes = true;
int row = 1;
int column = 1;
foreach (var item in dataTable.Columns)
{
Excel.Range range = workSheet.Cells[row, column] as Excel.Range;
range.NumberFormat = "#";
range.EntireColumn.AutoFit();
range.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray);
column++;
}
Excel.Range InternalCalculatedAmount = workSheet.Cells[1, 9] as Excel.Range;
InternalCalculatedAmount.EntireColumn.NumberFormat = "#0.00";
InternalCalculatedAmount.Columns.AutoFit();
workSheet.SaveAs(excelFilePath);
}
catch (System.Exception ex)
{
throw new System.Exception("Excel file could not be saved! Check filepath." + Environment.NewLine + ex.Message);
}
}
else // no filepath is given
{
excelApp.Visible = true;
}
}
catch (System.Exception ex)
{
throw new System.Exception("ex.Message + Environment.NewLine, ex.InnerException);
}
}
The exception thrown is a System.OutOfMemoryException when trying to execute the following piece of code:
workSheet.get_Range(excelRange, Type.Missing).Value2 = rawData;
My biggest frustration is that this method works 100% in a regular C# application.
The DataTable contains about 435000 rows. I know it's quite a bit of data but I use this very method, modified of course, to split data across multiple Excel worksheets in one of my other applications, and that DataSet contains about 1.1m rows. So less than half of my largest DataSet should be a walk-in-the-park...
Any light shed on this matter would be amazing!
Related
I would like to accelerate the process of deleting columns in Excel. I have 2 Excel files. Let's say data file where all data exist and the deleting column names in other file. There are many worksheets in data file some have more than 15000 columns and around 2500 rows in deleting column file. It is taking longer time to delete. My question how could I speed up the code below?
private void workerdelete_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
totalRow = xlWorksheetdelvariable.UsedRange.Rows.Count;
Excel.Range startCell = xlWorksheetdelvariable.Cells[2, 1];
Excel.Range endCell = xlWorksheetdelvariable.Cells[totalRow, 1];
Excel.Range currentRange = xlWorksheetdelvariable.get_Range(startCell, endCell).Cells;
object[,] matrixReaddelvariable = (object[,])currentRange.Value;
foreach (Excel.Worksheet sheet in xlWorkbookDelete.Sheets)
{
xlWorksheetdelete = xlWorkbookDelete.Sheets[sheet.Index];
columnCount = xlWorksheetdelete.UsedRange.Columns.Count;
int rowCount = xlWorksheetdelete.UsedRange.Rows.Count;
Excel.Range startCelldelete = xlWorksheetdelete.Cells[1, 1];
Excel.Range endCelldelete = xlWorksheetdelete.Cells[1, columnCount];
Excel.Range currentRangedelete = xlWorksheetdelete.get_Range(startCelldelete, endCelldelete).Cells;
object[,] matrixReaddelete = (object[,])currentRangedelete.Value;
List<int> delCols = new List<int>();
for (int c = 1; c <= columnCount; c++)
{
for (int r = 1; r < totalRow; r++)
{
if (dellabel == true)
{
if (matrixReaddelete[1, c].ToString() == matrixReaddelvariable[r, 1].ToString()||matrixReaddelete[1, c].ToString().Contains("_label"))
{
delCols.Insert(0, c);
break;
}
}
else if(matrixReaddelete[1, c].ToString() == matrixReaddelvariable[r, 1].ToString())
{
delCols.Insert(0, c);
break;
}
}
int percentage = (c + 1) * 50 / columnCount;
workerdelete.ReportProgress(percentage);
}
foreach (int colIndex in delCols)
{
xlWorksheetdelete.Columns[colIndex].Delete();
int percentage = 50 + ( delCols.IndexOf(colIndex) + 1 ) * 50 / delCols.Count;
workerdelete.ReportProgress(percentage);
}
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
private void LoadExcelSheet(string path, int sheet){
_Application excel = new Excel.Application();
Workbook wb;
Worksheet ws;
string data = "";
int row = 0;
int col = 0;
wb = excel.Workbooks.Open(path);
ws = wb.Worksheets[sheet];
listBox1.Items.Clear();
for (row = 1; row < 10; row++){
data = " ";
for (col = 1; col < 3; col++) {
data += ws.Cells[row, col].Value2 + " ";
}
//wanted to filter out empty cells/data and at the same time count
//number of items in the list... row should stop.. I think!
if(data == null){
break;
}
listBox1.Items.Add(data);
}
The if statement doesn't seems to work no matter what I do. I would appreciate it very if anyone could point me in the right direction.
use it like this:
if (data.Trim().Length < 1)
{
return;
}
use return not break
Add a condition like this
If(string.IsNullOrEmpty(data))
{
Break;
}
I'm working with DataTable's and I need to convert them to a CSV file format. Most of the tables I am working with have over 50,000 records so I'm trying to minimize the time it takes to convert them.
Here is my current method:
public static string table_to_csv(DataTable table)
{
string file = "";
foreach (DataColumn col in table.Columns)
file = string.Concat(file, col.ColumnName, ",");
file = file.Remove(file.LastIndexOf(','), 1);
file = string.Concat(file, "\r\n");
foreach (DataRow row in table.Rows)
{
foreach (object item in row.ItemArray)
file = string.Concat(file, item.ToString(), ",");
file = file.Remove(file.LastIndexOf(','), 1);
file = string.Concat(file, "\r\n");
}
return file;
}
Is there any way I can improve the efficiency of this method? I'm welcome to any modifications and ideas that you have!
Use a System.Text.StringBuilder for huge strings - that's pretty fast.
I implemented this one:
public static string DataTableToCSV(this DataTable datatable, char seperator)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < datatable.Columns.Count; i++)
{
sb.Append(datatable.Columns[i]);
if (i < datatable.Columns.Count - 1)
sb.Append(seperator);
}
sb.AppendLine();
foreach (DataRow dr in datatable.Rows)
{
for (int i = 0; i < datatable.Columns.Count; i++)
{
sb.Append(dr[i].ToString());
if (i < datatable.Columns.Count - 1)
sb.Append(seperator);
}
sb.AppendLine();
}
return sb.ToString();
}
Here's a method I have in my Utility class. Works well for what I'm doing.
public static void GenerateCSV(DataTable dt)
{
StringBuilder sb = new StringBuilder();
try
{
int count = 1;
int totalColumns = dt.Columns.Count;
foreach (DataColumn dr in dt.Columns)
{
sb.Append(dr.ColumnName);
if (count != totalColumns)
{
sb.Append(",");
}
count++;
}
sb.AppendLine();
string value = String.Empty;
foreach (DataRow dr in dt.Rows)
{
for (int x = 0; x < totalColumns; x++)
{
value = dr[x].ToString();
if (value.Contains(",") || value.Contains("\""))
{
value = '"' + value.Replace("\"", "\"\"") + '"';
}
sb.Append(value);
if (x != (totalColumns - 1))
{
sb.Append(",");
}
}
sb.AppendLine();
}
}
catch (Exception ex)
{
// Do something
}
}
I have used this method which copies object array to an Excel cell range rather than copying row by row and then column by column & it proved to be quite efficient way
public void ExportToExcel(DataTable dataTable, String pathToSave)
{
// Create the Excel Application object
var excelApp = new ApplicationClass();
// Create a new Excel Workbook
Workbook excelWorkbook = excelApp.Workbooks.Add(Type.Missing);
int sheetIndex = 0;
// Copy the DataTable to an object array
var rawData = new object[dataTable.Rows.Count + 1, dataTable.Columns.Count];
// Copy the column names to the first row of the object array
for (var col = 0; col < dataTable.Columns.Count; col++)
{
rawData[0, col] = dataTable.Columns[col].ColumnName;
}
// Copy the values to the object array
for (var col = 0; col < dataTable.Columns.Count; col++)
{
for (int row = 0; row < dataTable.Rows.Count; row++)
{
rawData[row + 1, col] = dataTable.Rows[row].ItemArray[col];
}
}
// Calculate the final column letter
string finalColLetter = string.Empty;
const string colCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int colCharsetLen = colCharset.Length;
if (dataTable.Columns.Count > colCharsetLen)
{
finalColLetter = colCharset.Substring(
(dataTable.Columns.Count - 1) / colCharsetLen - 1, 1);
}
finalColLetter += colCharset.Substring((dataTable.Columns.Count - 1) % colCharsetLen, 1);
// Create a new Sheet
var excelSheet = (Worksheet)excelWorkbook.Sheets.Add(excelWorkbook.Sheets.Item[++sheetIndex], Type.Missing, 1, XlSheetType.xlWorksheet);
excelSheet.Name = dataTable.TableName;
// Fast data export to Excel
var excelRange = string.Format("A1:{0}{1}", finalColLetter, dataTable.Rows.Count + 1);
excelSheet.Range[excelRange, Type.Missing].Value2 = rawData;
// Mark the first row as BOLD and BLUE
var headerColumnRange = (Range)excelSheet.Rows[1, Type.Missing];
headerColumnRange.Font.Bold = true;
headerColumnRange.Font.Color = 0xFF0000;
headerColumnRange.EntireColumn.AutoFit();
// Save and Close the Workbook
excelWorkbook.SaveAs(pathToSave, XlFileFormat.xlWorkbookNormal, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, XlSaveAsAccessMode.xlExclusive,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
excelWorkbook.Close(true, Type.Missing, Type.Missing);
excelWorkbook = null;
// Release the Application object
excelApp.Quit();
excelApp = null;
// Collect the unreferenced objects
GC.Collect();
GC.WaitForPendingFinalizers();
}
I have this working C# code to export Sql Server data to excel. The problem is one column contains long int and it appears in excel as 6.71524E+11. So I understand that we have to convert it as string in excel.
How to implement that in my code? Examples would be appreciated.
public static void ExportToExcel(DataTable dt)
{
try
{
string conString = "Data Source=DELL\\SQLSERVER1;Trusted_Connection=True;DATABASE=Camo;CONNECTION RESET=FALSE";
SqlConnection sqlCon = new SqlConnection(conString);
sqlCon.Open();
SqlDataAdapter da = new SqlDataAdapter("select TOP 10000 LocalSKU,ItemName, QOH,Price,Discontinued,Barcode,Integer2,Integer3,SalePrice,SaleOn,Price2 from dbo.Inventory", sqlCon);
System.Data.DataTable dtMainSQLData = new System.Data.DataTable();
da.Fill(dtMainSQLData);
DataColumnCollection dcCollection = dtMainSQLData.Columns;
// Export Data into EXCEL Sheet
Microsoft.Office.Interop.Excel.ApplicationClass ExcelApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
ExcelApp.Application.Workbooks.Add(Type.Missing);
int i = 1;
int j = 1;
//header row
foreach (DataColumn col in dtMainSQLData.Columns)
{
ExcelApp.Cells[i, j] = col.ColumnName;
j++;
}
i++;
//data rows
foreach (DataRow row in dtMainSQLData.Rows)
{
for (int k = 1; k < dtMainSQLData.Columns.Count + 1; k++)
{
ExcelApp.Cells[i, k] = row[k - 1].ToString();
}
i++;
}
ExcelApp.ActiveWorkbook.SaveCopyAs("C:/Users/Administrator.CAMO/Downloads/FtpFilesStorage/Export/Sheet1.xlsx");
ExcelApp.ActiveWorkbook.Saved = true;
ExcelApp.Quit();
Console.WriteLine(".xlsx file Exported succssessfully.");
}
This method work for exporting to an Excel file.
private void ExportToExcel(DataTable Tbl, string ExcelFilePath = null)
{
try
{
if (Tbl == null || Tbl.Columns.Count == 0)
throw new Exception("ExportToExcel: Null or empty input table!\n");
// load excel, and create a new workbook
Excel.Application excelApp = new Excel.Application();
//Excel.Workbook ExcelBookServices = excelApp.Workbooks.Add();
excelApp.Workbooks.Add();
// single worksheet
Excel._Worksheet workSheet = (Excel._Worksheet)excelApp.ActiveSheet;
// column headings
for (int i = 0; i < Tbl.Columns.Count; i++)
{
workSheet.Cells[1, (i + 1)] = Tbl.Columns[i].ColumnName;
}
// rows
for (int i = 0; i < Tbl.Rows.Count; i++)
{
// to do: format datetime values before printing
for (int j = 0; j < Tbl.Columns.Count; j++)
{
workSheet.Cells[(i + 2), (j + 1)] = Tbl.Rows[i][j];
}
}
workSheet.Columns.AutoFit();
//workSheet.Columns.Style = "Output";
//Excel.Range cell = ((Excel.Range)workSheet.Cells[Tbl.Rows.Count, Tbl.Columns.Count]);
// check fielpath
if (ExcelFilePath != null && ExcelFilePath != "")
{
try
{
workSheet.SaveAs(ExcelFilePath);
excelApp.Quit();
//File Saved Message
}
catch (Exception ex)
{
//ExportToExcel: Excel file could not be saved! Check filepath Message
}
}
else // no filepath is given
{
excelApp.Visible = true;
}
}
catch (Exception ex)
{
//Error in creating the Excel file
ScriptManager.RegisterStartupScript(this, GetType(), "Message", "ExportToExcel: \n" + ex.Message, true);
}
}
Change the for loop to
int i = 1;
int j = 1;
//header row
foreach (DataColumn col in dtMainSQLData.Columns)
{
ExcelApp.Cells[i, j] = col.ColumnName;
j++;
ExcelApp.Rows.AutoFit();
ExcelApp.Columns.AutoFit();
}
i++;
Console.Write("Progressing......65% \n Wait for around 8 minutes \r");
//data rows
foreach (DataRow row in dtMainSQLData.Rows)
{
for (int k = 1; k < dtMainSQLData.Columns.Count + 1; k++)
{
ExcelApp.Cells[i, k] = "'" + row[k - 1].ToString();
}
i++;
ExcelApp.Columns.AutoFit();
ExcelApp.Rows.AutoFit();
}
use the property NumberFormat:
MyWorkBook.NumberFormat = "#";
i have Devxpress GridControl on the form,
and i want to send data on this grid to excel.
and i dont want to do this with ExportToExcel method
i have googled and found this code
but this code is for DataGrid control of .Net
and it gives an error when it tries to convert
DevExpress.XtraGrid.Views.Grid.GridView to System.Data.DataView
here is the code
public string LastCoulmLetter(int coulmnCount)
{
string finalColLetter = string.Empty;
string colCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int colCharsetLen = colCharset.Length;
if (coulmnCount > colCharsetLen)
{
finalColLetter = colCharset.Substring(
(coulmnCount - 1) / colCharsetLen - 1, 1);
}
finalColLetter += colCharset.Substring(
(coulmnCount - 1) % colCharsetLen, 1);
return finalColLetter;
}
public void FromGridToExcel()
{
if (gridView1.RowCount <= 0)
return;
Excel.Application xls = new Excel.Application();
Excel.Workbook wb;
Excel.Worksheet sheet;
object SalakObje = System.Reflection.Missing.Value;
wb = xls.Workbooks.Add(SalakObje);
sheet = (Excel.Worksheet)wb.ActiveSheet;
sheet.Name = "Result";
xls.Visible = true;
DataTable dt = (DataTable)gridView1.DataSource; // Error comes in here
// Copy the DataTable to an object array
object[,] rawData = new object[dt.Rows.Count + 1, dt.Columns.Count];
// Copy the column names to the first row of the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
rawData[0, col] = dt.Columns[col].ColumnName;
}
// Copy the values to the object array
for (int col = 0; col < dt.Columns.Count; col++)
{
for (int row = 0; row < dt.Rows.Count; row++)
{
rawData[row + 1, col] = dt.Rows[row].ItemArray[col];
}
}
// Fast data export to Excel
string excelRange = string.Format("A1:{0}{1}",LastCoulmLetter(dt.Columns.Count), dt.Rows.Count + 1);
sheet.get_Range(excelRange, Type.Missing).Value2 = rawData;
sheet.get_Range(excelRange).Columns.AutoFit();
}
So what is the problem and how to fix it
The problem appears to be that your DataSource is a DataView, not a DataTable.
Some options:
Cast it to a DataView if you want to use the same filters:
DataView dv = (DataView)gridView1.DataSource;
Use the .Table property to get the source table if you want the raw data:
DataTable dt = ((DataView)gridView1.DataSource).Table;
Try this instead:
DataTable dt = ((DataView)gridView1.DataSource).Table;