[英]Export a large data query (60k+ rows) to Excel
我創建了一個報告工具,作為內部Web應用程序的一部分。 該報告將所有結果顯示在GridView中,並且我使用JavaScript將GridView的內容逐行讀取到Excel對象中。 JavaScript繼續在另一個工作表上創建數據透視表。
不幸的是,如果返回幾天以上,我沒想到GridView的大小會導致瀏覽器超載問題。 該應用程序每天有數千條記錄,比如說每月60k,理想情況下,我希望能夠返回長達一年的所有結果。 行數導致瀏覽器掛起或崩潰。
我們正在SQL Server的Visual Studio 2010上使用ASP.NET 3.5,預期的瀏覽器是IE8。 該報告由一個gridview組成,該gridview根據用戶選擇的人群從少數幾個存儲過程中獲取一個數據。 gridview在UpdatePanel中:
<asp:UpdatePanel ID="update_ResultSet" runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_Submit" />
</Triggers>
<ContentTemplate>
<asp:Panel ID="pnl_ResultSet" runat="server" Visible="False">
<div runat="server" id="div_ResultSummary">
<p>This Summary Section is Automatically Completed from Code-Behind</p>
</div>
<asp:GridView ID="gv_Results" runat="server"
HeaderStyle-BackColor="LightSkyBlue"
AlternatingRowStyle-BackColor="LightCyan"
Width="100%">
</asp:GridView>
</div>
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
我對團隊來說相對較新,因此我遵循他們的典型做法,將存儲過程返回到DataTable並將其用作后面代碼中的DataSource:
List<USP_Report_AreaResult> areaResults = new List<USP_Report_AreaResult>();
areaResults = db.USP_Report_Area(ddl_Line.Text, ddl_Unit.Text, ddl_Status.Text, ddl_Type.Text, ddl_Subject.Text, minDate, maxDate).ToList();
dtResults = Common.LINQToDataTable(areaResults);
if (dtResults.Rows.Count > 0)
{
PopulateSummary(ref dtResults);
gv_Results.DataSource = dtResults;
gv_Results.DataBind();
(我知道您在想什么!但是,是的,自那時以來,我已經學到了更多有關參數化的知識。)
LINQToDataTable函數沒有什么特別的,只是將列表轉換為數據表。
有幾千條記錄(最多幾天),這可以正常工作。 GridView顯示結果,並且有一個供用戶單擊的按鈕,它將啟動JScript導出器。 外部JavaScript函數將每一行讀入Excel工作表,然后使用該行創建數據透視表。 數據透視表很重要!
function exportToExcel(sMyGridViewName, sTitleOfReport, sHiddenCols) {
//sMyGridViewName = the name of the grid view, supplied as a text
//sTitleOfReport = Will be used as the page header if the spreadsheet is printed
//sHiddenCols = The columns you want hidden when sent to Excel, separated by semicolon (i.e. 1;3;5).
// Supply an empty string if all columns are visible.
var oMyGridView = document.getElementById(sMyGridViewName);
//If no data is on the GridView, display alert.
if (oMyGridView == null)
alert('No data for report');
else {
var oHid = sHiddenCols.split(";"); //Contains an array of columns to hide, based on the sHiddenCols function parameter
var oExcel = new ActiveXObject("Excel.Application");
var oBook = oExcel.Workbooks.Add;
var oSheet = oBook.Worksheets(1);
var iRow = 0;
for (var y = 0; y < oMyGridView.rows.length; y++)
//Export all non-hidden rows of the HTML table to excel.
{
if (oMyGridView.rows[y].style.display == '') {
var iCol = 0;
for (var x = 0; x < oMyGridView.rows(y).cells.length; x++) {
var bHid = false;
for (iHidCol = 0; iHidCol < oHid.length; iHidCol++) {
if (oHid[iHidCol].length !=0 && oHid[iHidCol] == x) {
bHid = true;
break;
}
}
if (!bHid) {
oSheet.Cells(iRow + 1, iCol + 1) = oMyGridView.rows(y).cells(x).innerText;
iCol++;
}
}
iRow++;
}
}
我正在嘗試做的事情:創建一個可以處理此數據並將其處理到Excel中的解決方案(可能是客戶端)。 有人可能建議使用HtmlTextWriter ,但是afaik不允許自動生成數據透視表並創建令人討厭的彈出警告。...
我嘗試過的
更新:我對替代解決方案仍然很開放,但是我一直在追求JSON理論。 我有一個有效的服務器端方法,該方法從DataTable生成JSON對象。 我不知道如何將JSON傳遞到(外部)exportToExcel JavaScript函數中。
protected static string ConstructReportJSON(ref DataTable dtResults)
{
StringBuilder sb = new StringBuilder();
sb.Append("var sJSON = [");
for (int r = 0; r < dtResults.Rows.Count; r++)
{
sb.Append("{");
for (int c = 0; c < dtResults.Columns.Count; c++)
{
sb.AppendFormat("\"{0}\":\"{1}\",", dtResults.Columns[c].ColumnName, dtResults.Rows[r][c].ToString());
}
sb.Remove(sb.Length - 1, 1); //Truncate the trailing comma
sb.Append("},");
}
sb.Remove(sb.Length - 1, 1);
sb.Append("];");
return sb.ToString();
}
任何人都可以顯示一個如何將此JSON對象帶入外部JS函數的示例嗎? 或任何其他導出到Excel的解決方案。
編寫CSV文件既簡單又有效。 但是, 如果您需要Excel,它也可以以相當有效的方式完成,通過使用Microsoft Open XML SDK的open XML Writer可以處理60,000多行。
只需在以下網址查看Vincent Tan的解決方案即可: http://polymathprogrammer.com/2012/08/06/how-to-properly-use-openxmlwriter-to-write-large-excel-files/ (下面,我稍微整理了一下他的示例以幫助新用戶。)
在我自己的使用中,我發現常規數據非常簡單,但是我確實必須從真實數據中去除“ \\ 0”字符。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
...
using (var workbook = SpreadsheetDocument.Create("SomeLargeFile.xlsx", SpreadsheetDocumentType.Workbook))
{
List<OpenXmlAttribute> attributeList;
OpenXmlWriter writer;
workbook.AddWorkbookPart();
WorksheetPart workSheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
for (int i = 1; i <= 50000; ++i)
{
attributeList = new List<OpenXmlAttribute>();
// this is the row index
attributeList.Add(new OpenXmlAttribute("r", null, i.ToString()));
writer.WriteStartElement(new Row(), attributeList);
for (int j = 1; j <= 100; ++j)
{
attributeList = new List<OpenXmlAttribute>();
// this is the data type ("t"), with CellValues.String ("str")
attributeList.Add(new OpenXmlAttribute("t", null, "str"));
// it's suggested you also have the cell reference, but
// you'll have to calculate the correct cell reference yourself.
// Here's an example:
//attributeList.Add(new OpenXmlAttribute("r", null, "A1"));
writer.WriteStartElement(new Cell(), attributeList);
writer.WriteElement(new CellValue(string.Format("R{0}C{1}", i, j)));
// this is for Cell
writer.WriteEndElement();
}
// this is for Row
writer.WriteEndElement();
}
// this is for SheetData
writer.WriteEndElement();
// this is for Worksheet
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(workbook.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
// you can use object initialisers like this only when the properties
// are actual properties. SDK classes sometimes have property-like properties
// but are actually classes. For example, the Cell class has the CellValue
// "property" but is actually a child class internally.
// If the properties correspond to actual XML attributes, then you're fine.
writer.WriteElement(new Sheet()
{
Name = "Sheet1",
SheetId = 1,
Id = workbook.WorkbookPart.GetIdOfPart(workSheetPart)
});
writer.WriteEndElement(); // Write end for WorkSheet Element
writer.WriteEndElement(); // Write end for WorkBook Element
writer.Close();
workbook.Close();
}
如果查看該代碼,您會注意到兩個主要的內容,首先是工作表,然后是包含工作表的工作簿。 工作簿部分最后是無聊的部分,較早的工作表部分包含所有行和列。
在您自己的修改中,您可以將自己的數據中的實際字符串值寫入單元格中。 相反,在上面,我們僅使用行和列編號。
writer.WriteElement(new CellValue("SomeValue"));
值得注意的是,Excel中的行編號從1開始而不是0。從零索引開始的行編號將導致錯誤消息“ Corrupt file”。
最后,如果您要處理大量數據,則永遠不要調用ToList() 。 使用數據讀取器樣式的方法來流傳輸數據 。 例如,您可以擁有一個IQueryable並將其用於 。 您永遠不需要真正依賴於同時將所有數據都存儲在內存中,否則您將遇到內存不足的限制和/或較高的內存利用率。
我會嘗試使用displaytag顯示結果。 您可以將其設置為每頁顯示一定數量,這應該可以解決您的超載問題。 然后,您可以設置displaytag以允許Excel導出。
我們通常使用“導出”命令按鈕來處理此問題,該按鈕已連接至服務器端方法以獲取數據集並將其轉換為CSV。 然后我們調整響應頭,瀏覽器會將其視為下載。 我知道這是一個服務器端解決方案,但是您可能要考慮一下,因為在實現服務器端記錄分頁之前,您將繼續遇到超時和瀏覽器問題。
自從我開始解決此問題以來,已經有近半個星期的時間了,我終於設法在一定程度上解決了所有問題。 我將暫時暫不標記答案,以查看是否有人有更有效,更好的“最佳做法”方法。
通過生成JSON字符串,我將JavaScript與GridView分離了。 填充數據時,會在后面的代碼中生成JSON:
protected static string ConstructReportJSON(ref DataTable dtResults)
{
StringBuilder sb = new StringBuilder();
for (int r = 0; r < dtResults.Rows.Count; r++)
{
sb.Append("{");
for (int c = 0; c < dtResults.Columns.Count; c++)
{
sb.AppendFormat("\"{0}\":\"{1}\",", dtResults.Columns[c].ColumnName, dtResults.Rows[r][c].ToString());
}
sb.Remove(sb.Length - 1, 1); //Truncate the trailing comma
sb.Append("},");
}
sb.Remove(sb.Length - 1, 1);
return String.Format("[{0}]", sb.ToString());
}
返回一串數據,例如
[{“ Caller”:“ John Doe”,“ Office”:“ 5555”,“ Type”:“ Incoming”等,
{“呼叫者”:“簡・多伊”,“辦公室”:“ 7777”,“類型”:“外出”,等等},{etc}]
我通過使用以下方式將文本分配給UpdatePanel中的Literal來隱藏了此字符串:
<div id="div_JSON" style="display: none;">
<asp:Literal id="lit_JSON" runat="server" />
</div>
JavaScript通過讀取div的內容來解析輸出:
function exportToExcel_Pivot(sMyJSON, sTitleOfReport, sReportPop) {
//sMyJSON = the name, supplied as a text, of the hidden element that houses the JSON array.
//sTitleOfReport = Will be used as the page header if the spreadsheet is printed.
//sReportPop = Determines which business logic to create a pivot table for.
var sJSON = document.getElementById(sMyJSON).innerHTML;
var oJSON = eval("(" + sJSON + ")");
// DEBUG Example Test Code
// for (x = 0; x < oJSON.length; x++) {
// for (y in oJSON[x])
// alert(oJSON[x][y]); //DEBUG, returns field value
// alert(y); //DEBUG, returns column name
// }
//If no data is in the JSON object array, display alert.
if (oJSON == null)
alert('No data for report');
else {
var oExcel = new ActiveXObject("Excel.Application");
var oBook = oExcel.Workbooks.Add;
var oSheet = oBook.Worksheets(1);
var oSheet2 = oBook.Worksheets(2);
var iRow = 0;
var iCol = 0;
//Take the column names of the JSON object and prepare them in Excel
for (header in oJSON[0])
{
oSheet.Cells(iRow + 1, iCol + 1) = header;
iCol++;
}
iRow++;
//Export all rows of the JSON object to excel
for (var r = 0; r < oJSON.length; r++)
{
iCol = 0;
for (c in oJSON[r])
{
oSheet.Cells(iRow + 1, iCol + 1) = oJSON[r][c];
iCol++;
} //End column loop
iRow++;
} //End row
字符串輸出和JavaScript“ eval”解析都非常快地工作,但是遍歷JSON對象比我想要的慢一點。
我相信這種方法將限於大約10億個字符的數據-可能更少,具體取決於內存測試的工作方式。 (我計算過,每天可能最多查看100萬個字符,所以在報告的一年之內就可以了。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.