【问题标题】:Kendo grid performance issue (Kendo Grid + Angular JS + Web API)剑道网格性能问题(剑道网格 + Angular JS + Web API)
【发布时间】:2018-09-21 03:51:28
【问题描述】:

问题:

我的 Angular JS html 页面中有 Kendo Grid。 Kendo Grid 数据来自我的远程服务 Web API。

当我们进行分页(或第一次加载页面)时,Kendo 网格尝试在浏览器中为每 10 条记录下载 38 MB 的内容,这大约需要 10 分钟。 6 分钟加载数据。

38 MB 下载了什么内容?

我已经通过阅读/学习堆栈溢出的类似支持票来实现:

  1. 已实现服务器分页 true(pageSize = 10,总记录 = 大约 56000)

  2. JS 和 CSS 的捆绑

  3. 我尝试了以下两个选项:

    可滚动:{虚拟:真实} 要么 可滚动:{ 无尽:真 }

  4. 我在生产中检查了我的存储过程,它在不到 3 秒的时间内执行了大约。 55000 条记录。 (在生产和暂存两台服务器上)。

  5. 我检查了我的 Web API 控制器,它在不到 4 秒的时间内将响应返回到 kendo grid,然后 kendo grid 需要太多时间来填充数据。

  6. 我有以下用于剑道的 JS 和 CSS(已经实现捆绑):

    • Kendo.all.min.js
    • kendo.bootstrap.min.css
    • kendo.common-bootstrap.min.css
    • 项目所需的其他 JS 和 CSS 也以捆绑方式加载。

以下是我的实时项目页面:

HTML 页面:

<div id="heatMapGrid" kendo-grid k-options="vm.heatMapGridOptions"></div>

AngularJS 控制器:

var dataSourceHeatMapGrid = new kendo.data.DataSource({
            transport: {
                read: function (options) {

                    heatMapService.getHeatMapGrid(options.data, heatMapGridParams)
                        .then(function (response) {
                            options.success(response.data);
                            $rootScope.optioncallback = options;

                            //$scope.htmapGridCSV = [];
                            //$scope.htmapGridCSV = response.data.exportData;

                        }).catch(function (e) {
                            console.log('Error: ', e);
                            throw e;
                        });
                },
                parameterMap: function (options) {
                    return JSON.stringify(options);
                }
            },
            schema: {
                data: function (response) {
                    return response.gridData;
                },
                total: function (response) {
                    return response.Total;
                },
                model: {
                    fields: {
                        TPID: { type: "number" },
                        TPName: { type: "string" },
                        EndCustomerPurchaseAmt: { type: "number" },
                        PrimaryExpirationMonth: { type: "string" },
                        AgreementID: { type: "number" },
                        TotalPurchased: { type: "number" },
                        TotalAssigned: { type: "number" },
                        OverUnder: { type: "number" },
                        VSEntPurchasedUnits: { type: "number" },
                        VSProMSDNUnits: { type: "number" },
                        VSTestProMSDNUnits: { type: "number" },
                        MSDNPlat: { type: "number" },
                        CloudPurchasedUnits: { type: "number" },
                        UnbilledOverage: { type: "number" },
                        AzurePotentialRevenue: { type: "number" }
                    }
                }
            },
            pageSize: 10,
            serverPaging: true
        });

vm.heatMapGridOptions = {
            columns: [
                { "title": "", template: "<a title='#=TPID#' #=isPinnedAccount==1 ? \"class='terrunpinaccount'\" : \"class='terrpinaccount'\"# ng-click='vm.pinUnpinAccount(\"#=TPID#\")'></a>" },
                { "title": "Account Name", "field": "TPName", template: "<a href='javascript:void(0);' ng-click='vm.tPIDDetails(\"#=TPID#\",\"#=TPName#\")' title='#=TPName#'><div class='DisplayTitleTPName'>#=TPName#<ul><li>AM: #=AM#, OM: #=OperatingModel#, Country: #=Country#</li><li>DevSales Lead: #=SalesLead#, SSP: #=Dev_SSP#, TSP: #=DevTSP#</li></ul></div></a>", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                {
                    "title": "PERFORMANCE AND ANNIVERSARIES", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                    columns:
                    [{
                        "title": "Renewals and True Ups", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                        columns: [{ "title": "Total Annualized Expiring", "field": "EndCustomerPurchaseAmt", format: "{0:c0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Primary Anniversary Month", "field": "PrimaryExpirationMonth", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Agreement Number", "field": "AgreementID", format: "{0:n0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } }]
                    }]
                },
                {
                    "title": "EFFECTIVE LICENSE POSITIONS", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                    columns:
                    [{
                        "title": "Visual Studio Subscriptions", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                        columns: [{ "title": "Purchased", "field": "TotalPurchased", format: "{0:n0}" },
                                { "title": "Assigned", "field": "TotalAssigned", format: "{0:n0}" },
                                { "title": "Over Under", "field": "OverUnder", format: "{0:n0}" }]
                    },
                    {
                        "title": "Account Footprint (Active SA Licenses)", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                        columns: [{ "title": "Enterprise w/ MSDN", "field": "VSEntPurchasedUnits", format: "{0:n0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Pro w/ MSDN", "field": "VSProMSDNUnits", format: "{0:n0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Test Pro w/ MSDN", "field": "VSTestProMSDNUnits", format: "{0:n0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "MSDN Platforms", "field": "MSDNPlat", format: "{0:n0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Cloud", "field": "CloudPurchasedUnits", format: "{0:n0}" }]
                    },
                    {
                        "title": "Azure", headerAttributes: { style: "text-align: center;font-weight: bold;" },
                        columns: [{ "title": "Unbilled Overage", "field": "UnbilledOverage", format: "{0:c0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } },
                                { "title": "Potential Revenue", "field": "AzurePotentialRevenue", headerTemplate: '<span title="Potential Revue is based on the delta of activated seats and <br/> developers deploying to Azure multiplied by the annual <br/> value of an Azure attached developer ($15k)">Potential Revenue</span>', format: "{0:c0}", headerAttributes: { style: "white-space: normal; overflow: visible;" } }]
                    }]
                }
            ],
            groupable: false,
            sortable: true,
            resizable: true,
            //pageable: true,
            pageable: {
                refresh: true,
                pageSizes: [10, 20, 50],

            },
            columnMenu: true,
            scrollable: false
            //filterable: true
        };

$("#heatMapGrid").data("kendoGrid").setDataSource(dataSourceHeatMapGrid);

AngularJS 服务:

services.getHeatMapGrid = function (command, heatMapGridParams) {
            var data = {
                page: command.page,
                pageSize: command.pageSize,
                skip: command.skip,
                take: command.take,
                alias: heatMapGridParams.alias,
                hasDevTest: heatMapGridParams.hasDevTest,
                hasDevTestLabs: heatMapGridParams.hasDevTestLabs,
                hasXamarin: heatMapGridParams.hasXamarin,
                devOpsMSShopFlag: heatMapGridParams.devOpsMSShopFlag,
                devOpsOSSThirdPartyShopsFlag: heatMapGridParams.devOpsOSSThirdPartyShopsFlag,
                intelligentAppsFlag: heatMapGridParams.intelligentAppsFlag,
                paaSServicesFlag: heatMapGridParams.paaSServicesFlag,
                enterpriseStepUpFlag: heatMapGridParams.enterpriseStepUpFlag,
                devsLearningAzureFlag: heatMapGridParams.devsLearningAzureFlag,
                devOpsAcceleratorEligibleFlag: heatMapGridParams.devOpsAcceleratorEligibleFlag,
                overAssignedFlag: heatMapGridParams.overAssignedFlag,
                lowAssignmentsFlag: heatMapGridParams.lowAssignmentsFlag,
                hasCloudSubscriptionFlag: heatMapGridParams.hasCloudSubscriptionFlag,
                hasUnbilledOverageFlag: heatMapGridParams.hasUnbilledOverageFlag,
                hasDevTestOppty: heatMapGridParams.hasDevTestOppty,
                areaID: heatMapGridParams.areaID,
                countryID: heatMapGridParams.countryID,
                segmentID: heatMapGridParams.segmentID,
                subsegmentID: heatMapGridParams.subsegmentID,
                salesUnitID: heatMapGridParams.salesUnitID,
                agreementRenewalOrTrueupID: heatMapGridParams.agreementRenewalOrTrueupID,
                aM: heatMapGridParams.aM,
                industry: heatMapGridParams.industry,
                hasAppServOppty: heatMapGridParams.hasAppServOppty,
                hasDotNetDeveloperFlag: heatMapGridParams.hasDotNetDeveloperFlag,
                paaSReadyFlag: heatMapGridParams.paaSReadyFlag,
                startMonth: heatMapGridParams.startMonth,
                endMonth: heatMapGridParams.endMonth
            };
            return $http({ method: 'GET', url: config.apiUrl + 'Account/HeatMapGrid/', params: data });
        };

网络 API:

[HttpGet]
    public heatMapGridAndExport HeatMapGrid([FromUri]HeatMapGridModel model)
    {
        ListView listView = new ListView();

        List<getHeatMapDataGlobalFilter_Result> listGridDataForTotalCount = new List<getHeatMapDataGlobalFilter_Result>();
        listGridDataForTotalCount = listView.GetListViewGridData(model.alias, model.hasDevTest, model.hasDevTestLabs, model.hasXamarin, model.devOpsMSShopFlag, model.devOpsOSSThirdPartyShopsFlag, model.intelligentAppsFlag, model.paaSServicesFlag, model.enterpriseStepUpFlag, model.devsLearningAzureFlag, model.devOpsAcceleratorEligibleFlag, model.overAssignedFlag, model.lowAssignmentsFlag, model.hasCloudSubscriptionFlag, model.hasUnbilledOverageFlag, model.hasDevTestOppty, model.areaID, model.countryID, model.segmentID, model.subsegmentID, model.salesUnitID, model.agreementRenewalOrTrueupID, model.aM, model.industry, model.hasAppServOppty, model.hasDotNetDeveloperFlag, model.paaSReadyFlag, model.startMonth, model.endMonth);

        List<getHeatMapDataGlobalFilter_Result> listGridData = new List<getHeatMapDataGlobalFilter_Result>();
        listGridData = listGridDataForTotalCount.Skip(model.skip).Take(model.take).OrderByDescending(c => c.EndCustomerPurchaseAmt).ToList();

                    //List<heatMapExport> listExportData = new List<heatMapExport>();
        //listExportData = listGridDataForTotalCount.Select(c => new heatMapExport()
        //{
        //    TPName = c.TPName,
        //    TPID = c.TPID,
        //    OperatingModel = c.OperatingModel,
        //    Area = c.Area,
        //    Country = c.Country,
        //    CreditedRegion = c.CreditedRegion,
        //    CreditedDistrict = c.CreditedDistrict,
        //    Segment = c.Segment,
        //    ATUManager = c.ATUManager,
        //    Dev_SSP = c.Dev_SSP,
        //    AM = c.AM,
        //    Industry = c.Industry,
        //    ATSName = c.ATSName,
        //    AssignedPect = string.Format("{0:p0}", c.AssignedPect),
        //    ActivatedPect = string.Format("{0:p0}", c.ActivatedPect),
        //    AzureActivated = Convert.ToString(c.AzureActivated),
        //    EndCustomerPurchaseAmt = string.Format("{0:c0}", c.EndCustomerPurchaseAmt),
        //    PrimaryExpirationMonth = Convert.ToString(c.PrimaryExpirationMonth),
        //    AgreementID = Convert.ToString(c.AgreementID),
        //    TotalPurchased = string.Format("{0:n0}", c.TotalPurchased),
        //    TotalAssigned = string.Format("{0:n0}", c.TotalAssigned),
        //    OverUnder = string.Format("{0:n0}", c.OverUnder),
        //    VSEntPurchasedUnits = string.Format("{0:n0}", c.VSEntPurchasedUnits),
        //    VSProMSDNUnits = string.Format("{0:n0}", c.VSProMSDNUnits),
        //    VSTestProMSDNUnits = string.Format("{0:n0}", c.VSTestProMSDNUnits),
        //    MSDNPlat = string.Format("{0:n0}", c.MSDNPlat),
        //    CloudPurchasedUnits = string.Format("{0:n0}", c.CloudPurchasedUnits),
        //    UnbilledOverage = string.Format("{0:c0}", c.UnbilledOverage),
        //    AzurePotentialRevenue = string.Format("{0:c0}", c.AzurePotentialRevenue)
        //}).ToList();
        var heatMapData = new heatMapGridAndExport
        {
            gridData = listGridData,
            //exportData = listExportData,
            Total = listGridDataForTotalCount.Count()
        };
        return heatMapData;
    }

我的环境:

  1. 版本 Telerik Control - Kendo UI v2017.2.621

  2. 操作系统开发机器 - Windows 10 Enterprise(8 GB RAM,Intel Core i7 处理器,64 位)(客户端操作系统)

  3. 浏览器 - Google Chrome,版本 65.0.3325.181

  4. .NET Framework - 版本 4.6.1

  5. Visual Studio - Enterprise 2015,版本 14.0.25431.01(更新 3)

  6. 编码语言 - C#

这是我的生产服务器浏览器屏幕截图:

这是我单击第二页时的另一个屏幕截图,它再次下载 38 mb 的内容,大约需要 38 mb。 6 分钟。 (服务器分页 = true 和 pageSize = 10)

代码调试截图:

我做错了什么?谁能帮帮我。

提前谢谢你。

【问题讨论】:

  • 您能否禁用将大约 56K 行导出数据附加到 WebAPI 响应的行并查看其执行情况? (为清楚起见,请在下面回答)。我怀疑这是你的问题
  • @JoeGlover,感谢您的回复。但我已经尝试过评论整个导出功能,但对下载的时间或内容没有影响。即删除导出功能没有任何改善。任何其他帮助表示赞赏。再次感谢。
  • 这将有助于准确查看 38Mb 中的内容,方法是在 WebAPI 返回响应之前断点(根据您之前的评论,我猜 heatMapData.gridData 因此必须包含整个数据集),或检查在线路上出现的 JSON。您应该能够在 Chrome 开发工具中深入研究,或者使用 Fiddler、Wireshark 等外部工具。
  • 要尝试的另一件事是您的 Linq 方法调用的顺序。我正在修改我原来的答案以表明我的意思。
  • @JoeGlover 以前我也有同样的想法,但后来我通过调试代码检查了 WebAPI 响应中的heatMapData.gridData 并检查了提琴手,但在这两次它都只包含 10 条记录而不是完整的数据集(因为我使用了跳过和获取)对于排序,我在跳过/获取之前通过交换排序子句进行了检查,但没有效果,还尝试通过删除排序,但两次都没有运气。任何其他帮助表示赞赏。谢谢

标签: javascript angularjs asp.net-web-api kendo-ui kendo-grid


【解决方案1】:

您能否禁用将大约 56K 行导出数据附加到 WebAPI 响应的行并查看其执行情况?我怀疑这是你的问题

    var heatMapData = new heatMapGridAndExport
    {
        gridData = listGridData,
        //exportData = listExportData,  //perhaps make conditional, for export only?
        Total = listGridDataForTotalCount.Count()
    };
    return heatMapData;

编辑:由于这似乎没有解决您的问题,您能否尝试像这样重新排序 Linq 方法调用,因为您得到的行为意味着整个结果集都回来了?

listGridData =
    listGridDataForTotalCount.OrderByDescending(c => c.EndCustomerPurchaseAmt)
        .Skip(model.skip).Take(model.take).ToList();

我不确定,但我想知道最后一个 OrderByDescending 是否会迫使 Linq 返回整个结果集,而这就是您最终从 ToList 得到的结果?

【讨论】:

  • 一些额外的上下文和描述会改善这个答案。
  • @JoeGlover,我尝试按照你说的重新排序 Linq 方法调用,但没有运气。
  • @KevinO,我们将不胜感激。
  • @JoeGlover,出乎意料的是,以前我删除了导出但对性能(和内容)没有影响,但现在我再次尝试了同样的方法,我发现导出数据导致了问题。我删除了它并使用 gzip 编码让它更快。谢谢
  • 这并不出人意料,它时常发生在我们身上!感谢您让我知道问题已解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
相关资源
最近更新 更多