asp.net-mvc - WCF服务生成Excel文件(10000行)并将其发送给客户端(性能问题)

标签 asp.net-mvc wcf serialization

在 Asp.net MVC 应用程序中,我们有一个页面用于下载动态生成的 Excel 报告。客户端应用程序调用 WCF 服务,该服务生成 Excel 文件并将文件名返回给客户端。 WCF 服务使用 OpenXML Sax 方法生成 excel 文件。

服务器调用存储过程并使用数据读取器来获取数据。通常该文件包含 10000 条记录。我们在测试环境中没有遇到任何性能问题。在生产中,如果有 10 个人访问该报告,则服务器内存将达到最大值,CPU 利用率也为 98%。因此,它会给该服务器中的所有应用程序带来问题。服务器只有 4GB RAM。我运行 4 个应用程序。通常我的应用程序需要更多内存。

这是代码:

public string GetMemberRosterHistoryFile(string VendorId, string versionId, DateTime FromDate, string ActionIndicator)
{
    string path = ConfigurationManager.AppSettings["FilePath"] + Guid.NewGuid() + ".xlsx";
    try
    {
        path = PathInfo.GetPath(path);
        log4net.ThreadContext.Properties["MethodName"] = "GetMemberRostersHistory";
        log.Info("Getting member rosters History");
        string sConn = ConfigurationManager.ConnectionStrings["VendorConnectContext"].ConnectionString;
        using (SqlConnection oConn = new SqlConnection(sConn))
        {
            oConn.Open();
            log.Debug("Connected");
            string sCmd = "pGetMemberRostersHistory";
            SqlCommand oCmd = new SqlCommand(sCmd, oConn);
            oCmd.CommandTimeout = Int32.MaxValue;
            oCmd.CommandType=CommandType.StoredProcedure;
            oCmd.Parameters.AddWithValue("@FromDate", FromDate.ToShortDateString());
            oCmd.Parameters.AddWithValue("@ActionIndicator", ActionIndicator);
            int index=1;
            StringBuilder programs = new StringBuilder();
            if (string.IsNullOrEmpty(versionId))
            {
                foreach (string value in GetPrograms(VendorId))
                {
                    if (index > 1)
                    {
                        programs.Append(",");
                    }
                    programs.Append(value);
                    index++;
                }
            }
            else
            {
                foreach (string value in GetPrograms(VendorId, versionId))
                {
                    if (index > 1)
                    {
                        programs.Append(",");
                    }
                    programs.Append(value);
                    index++;
                }
            }
            oCmd.Parameters.AddWithValue("@ProgramsList", programs.ToString());

            string[] FieldNames = new string[] 
                {
                        "ActionIndicator", 
                    "ChangeNotes",
                    "ActionEffectiveDate",
                    "MembershipTerminationDate",
                    "GPOId",
                    "GLN",
                    "HIN",
                    "Name1",
                    "Name2",
                    "AddressType",
                    "Address1",
                    "Address2",
                    "Address3",
                    "City",
                    "StateProvince",
                    "ZipPostalCode",
                    "Country",
                    "PhoneNbr",
                    "FaxNbr",
                    "RelationshipToGPO",
                    "RelationshipToDirectParent",
                    "DirectParentGPOId",
                    "DirectParentName1",
                    "TopParentGPOId",
                    "TopParentName1",
                    "MemberStatus",
                    "MembershipStartDate",
                    "OrganizationalStatus",
                    "ClassOfTradeName",
                    "DEA",
                    "DSHorHRSA",
                    "PHEffectiveDate",
                    "PHExpirationDate",
                    "BLPHEffectiveDate",
                    "BLPHExpirationDate",
                    "MMEffectiveDate",
                    "MMExpirationDate",
                    "BLMMEffectiveDate",
                    "BLMMExpirationDate",
                    "DIEffectiveDate",
                    "DIExpirationDate",
                    "LBEffectiveDate",
                    "LBExpirationDate",
                    "NMEffectiveDate",
                    "NMExpirationDate"
                    ,"BLMemberId"
                        ,"GPOCorporateGroup"
                        ,"GPOAffiliateGroup"
                        ,"GPO2AffiliateGroup"
                        ,"GPORelatedGroup"
                        ,"GPOIDNGroup"

                };
        string[] columnNames = new string[] 
                    {
                        "Action Indicator",
                        "Change Notes",
                        "Action Effective Date",
                        "Membership Termination Date",
                            "GPO ID",
                            "GLN",
                            "Health Industry Number (HIN)",
                            "Name 1",
                            "Name 2",
                            "Address Type",
                            "Address 1",
                            "Address 2",
                            "Address 3",
                            "City",
                            "State/Province",
                            "Postal Code",
                            "Country",
                            "Phone",
                            "Fax",
                            "Relationship to GPO",
                            "Relationship to Direct Parent",
                            "Direct Parent GPO ID",
                            "Direct Parent Name 1",
                            "Top Parent GPO ID",
                            "Top Parent Name 1",
                            "Member Status",
                            "Membership Start Date",
                            "Organizational Status",
                            "Class of Trade",
                            "DEA #",
                            "DSH and/or HRSA Number",
                            "Pharmacy Start Date",
                            "Pharmacy End Date",
                            "BL Pharmacy Start Date",
                            "BL Pharmacy End Date",
                            "Med Surg Start Date",
                            "Med Surg End Date",
                            "BL Med Surg Start Date",
                            "BL Med Surg End Date",
                            "Food Service Start Date",
                            "Food Service End Date",
                            "Laboratory Start Date",
                            "Laboratory End Date",
                            "NonMedical Start Date",
                            "NonMedical End Date"
                            ,"Broadlane ID"
                            ,"Corporate Group"
                            ,"Affiliate Group"
                            ,"2nd Affiliate Group"
                            ,"Related Group"
                        ,"IDN Group"
                    };
            //object result = oCmd.ExecuteScalar();
            //int count=(result!=null ? (int)result : 0);
            //oCmd.CommandText = "pGetMemberRostersHistory";
            //oCmd.CommandTimeout = Int32.MaxValue;
            using (SqlDataReader oReader = oCmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                SAXExcelExporter exporter = new SAXExcelExporter();
                exporter.Export(oReader, columnNames, FieldNames, path, "MemberRoster");

            }
        }
        return path;
    }
    catch (Exception ex)
    {
        log.Error("In exception", ex);
        return null;
    }
}

导出代码:

public void Export(SqlDataReader export, string[] columnNames, string[] fieldNames, string filename, string sheetName)
{
    Assembly _assembly = Assembly.GetExecutingAssembly();
    Stream stream = _assembly.GetManifestResourceStream("MA.VMS.Server.Template.xlsx");
    FileStream newfile = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
    stream.CopyTo(newfile);
    stream.Close();
    newfile.Close();
    using (SpreadsheetDocument myDoc = SpreadsheetDocument.Open(filename, true))
    {
        WorkbookPart workbookPart = myDoc.WorkbookPart;
        WorksheetPart worksheetPart = workbookPart.WorksheetParts.Last();
        string origninalSheetId = workbookPart.GetIdOfPart(worksheetPart);

        WorksheetPart replacementPart = workbookPart.AddNewPart<WorksheetPart>();
        string replacementPartId = workbookPart.GetIdOfPart(replacementPart);

        OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);
        OpenXmlWriter writer = OpenXmlWriter.Create(replacementPart);

        while (reader.Read())
        {
            if (reader.ElementType == typeof(SheetData))
            {
                if (reader.IsEndElement)
                    continue;
                writer.WriteStartElement(new SheetData());
    Row hr = new Row();
                writer.WriteStartElement(hr);

                for (int col = 0; col < columnNames.Length; col++)
                {
                    Cell c = new Cell();
                    c.DataType = CellValues.InlineString;
                    InlineString iss = new InlineString();
                    iss.AppendChild(new Text() { Text = columnNames[col] });
                    c.AppendChild(iss);
                    writer.WriteElement(c);
                }
    writer.WriteEndElement();

                //for (int row = -1; row < count; row++)
                while (export.Read())
                {

                    Row r = new Row();
                    writer.WriteStartElement(r);
                    //if (row == -1)
                    //{
                    //    for (int col = 0; col < columnNames.Length; col++)
                    //    {
                    //        Cell c = new Cell();
                    //        c.DataType = CellValues.InlineString;
                    //        InlineString iss = new InlineString();
                    //        iss.AppendChild(new Text() { Text = columnNames[col] });
                    //        c.AppendChild(iss);
                    //        writer.WriteElement(c);
                    //    }
                    //}
                    //else
                    //{
                        //export.Read();
                        for (int col = 0; col < fieldNames.Length; col++)
                        {
                            Cell c = new Cell();
                            c.DataType = CellValues.InlineString;
                            InlineString iss = new InlineString();
                            iss.AppendChild(new Text() { Text = GetValue(export[fieldNames[col]]) });

                            c.AppendChild(iss);
                            writer.WriteElement(c);
                        }
                    //}
                    writer.WriteEndElement();

                }

                writer.WriteEndElement();
            }
            else
            {
                if (reader.IsStartElement)
                {
                    writer.WriteStartElement(reader);
                }
                else if (reader.IsEndElement)
                {
                    writer.WriteEndElement();
                }
            }
        }

        reader.Close();
        writer.Close();

        Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().Where(s => s.Id.Value.Equals(origninalSheetId)).First();
        sheet.Id.Value = replacementPartId;
        workbookPart.DeletePart(worksheetPart);

    }
}

我很担心。当我查看 proc 时,26 秒内返回了数据,而 Excel 下载需要 3 分钟多。

遇到这种情况我该怎么办? 以下是我正在考虑的解决方案:

  1. 异步移动 Excel 下载并发送下载链接
  2. 在不同的服务器中部署 2 个应用程序。
  3. 在服务器上进行内存分析器

最佳答案

问题可能是 Open XML SDK 类和 SAX 方法类(特别是 OpenXmlWriter)的混合使用。 SDK 中有很多 DOM 包袱,这就是它们速度较慢的原因。

对于这个特殊情况,它是 Cell 类。整个Worksheet、SheetData和Row SDK类都是用OpenXmlWriter写出来的,但是Cell类仍然使用SDK版本的填充数据。这就是瓶颈。试试这个:

List<OpenXmlAttribute> oxa;
for (int col = 0; col < fieldNames.Length; col++)
{
    oxa = new List<OpenXmlAttribute>();
    // this is the data type ("t"), with CellValues.String ("str")
    oxa.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:
    //oxa.Add(new OpenXmlAttribute("r", null, "A1"));

    writer.WriteStartElement(new Cell(), oxa);

    writer.WriteElement(new CellValue(GetValue(export[fieldNames[col]])));

    // this is for Cell
    writer.WriteEndElement();
}

此外,CellValues.InlineString 用于内联富文本。如果您只有纯文本,则 CellValues.String 枚举值会更简单。

我前段时间也写过一篇文章。您可以find out more here .

关于asp.net-mvc - WCF服务生成Excel文件(10000行)并将其发送给客户端(性能问题),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17764102/

相关文章:

asp.net-mvc - 使用与本地化更改冲突的自定义数据注释进行验证

c# - 如何将 FileStreamResult 转换为 IFormFile?

asp.net - 通过 https 有选择地缓存 .js 和 .png 文件?

javascript - 在tinymce编辑器中加载html文件作为初始内容

javascript - 使用Ext.js从WCF获取数据 "Network Error 400 Bad Request"

wcf - ColdFusion cfinvoke(消息安全 - ClientCredentials = 用户名)

java - 如何使用 JAXB 编码有时包含 XML 内容有时不包含的字符串?

java - 如何防止 jackson 在第三方类中序列化一个字段

c# maxreceivedmessagesize 不工作

java - 使用 ObjectOutputStream 写入文件而不覆盖旧数据