我创建了一个报表工具,作为内部Web应用程序的一部分。该报告在GridView中显示所有结果,我使用JavaScript将逐行读取GridView的内容到Excel对象中。 JavaScript继续在不同的工作表上创建数据透视表。
不幸的是,如果返回的时间超过几天,我没想到GridView的大小会导致浏览器出现重载问题。该应用程序每天有几千条记录,比方说每月60k,理想情况下我希望能够将所有结果返回长达一年。行数导致浏览器挂起或崩溃。
我们在Visual Studio 2010上使用ASP.NET 3.5和SQL Server,预期的浏览器是IE8。该报告包含一个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>
我对我的团队来说比较新,所以我遵循他们将sproc返回到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的任何其他解决方案。
答案 0 :(得分:2)
编写CSV文件非常简单有效。但是,如果你需要Excel,它也可以以合理有效的方式完成,可以通过使用Microsoft Open XML SDK的开放XML Writer来处理60,000多行强>
只需查看Vincent Tan在http://polymathprogrammer.com/2012/08/06/how-to-properly-use-openxmlwriter-to-write-large-excel-files/的解决方案(下面,我稍微清理了他的示例,以帮助新用户。)
在我自己的使用中,我发现这是非常直接的常规数据,但我确实要剥离&#34; \ 0&#34;我的真实数据中的字符。
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();
}
如果您查看该代码,您将注意到两个主要的写入,首先是Sheet,然后是包含该工作表的工作簿。工作簿部分是最后的无聊部分,较早的工作表部分包含所有行和列。
在您自己的改编中,您可以根据自己的数据将真实的字符串值写入单元格。相反,在上面,我们只是使用行和列编号。
writer.WriteElement(new CellValue("SomeValue"));
值得注意的是,Excel中的行编号从1开始而不是0.从索引为零编号的起始行将导致&#34; Corrupt文件&#34;错误消息。
最后,如果您正在使用非常大的数据集,永远不会调用ToList()。使用数据流的数据读取器样式方法。例如,您可以拥有 IQueryable ,并在每个的中使用它。你真的不想依赖于同时在内存中存储所有数据,或者你会遇到内存不足和/或高内存利用率。
答案 1 :(得分:1)
我会尝试使用displaytag来显示结果。您可以将其设置为每页显示一定数量,这可以解决您的重载问题。然后,您可以设置displaytag以允许Excel导出。
答案 2 :(得分:1)
我们通常使用“导出”命令按钮处理此问题,该按钮连接到服务器端方法以获取数据集并将其转换为CSV。然后我们调整响应标头,浏览器会将其视为下载。我知道这是一个服务器端解决方案,但您可能需要考虑它,因为在实现服务器端记录分页之前,您将继续遇到超时和浏览器问题。
答案 3 :(得分:0)
自从我开始这个问题以来差不多一个半星期,我终于设法让它在某种程度上得到了解决。我会暂时等待标记答案,看看是否有其他人有更高效,更好的“最佳实践”方法。
通过生成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());
}
返回一串数据,例如
[{“来电者”:“John Doe”,“Office”:“5555”,“Type”:“Incoming”等},
{“来电者”:“Jane Doe”,“办公室”:“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万个字符,因此在报告后的一年内应该没问题。)