Kendo网格性能问题(Kendo Grid + Angular JS + Web API)

时间:2018-04-11 09:58:06

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

问题:

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

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

下载38 MB的内容和内容是什么?

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

  1. 实施服务器分页为真(pageSize = 10,总记录数= 56000)

  2. 捆绑JS和CSS

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

    scrollable:{virtual:true} 要么 scrollable:{endless:true}

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

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

  6. 我有以下JS和CSS for Kendo(已实施捆绑):

    • Kendo.all.min.js
    • kendo.bootstrap.min.css
    • kendo.common-bootstrap.min.css
    • 项目的其他所需JS和CSS也以捆绑方式加载。
  7. 以下是我的实时项目页面:

    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 });
            };
    

    Web 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. 浏览器 - 谷歌浏览器,版本65.0.3325.181

    4. .NET Framework - 版本4.6.1

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

    6. 编码语言 - C#

    7. 以下是我为生产服务器拍摄的浏览器屏幕:

      Here is my production server kendo grid, which takes almost 12 minutes to populate data in kendo grid, and you can see TTFB (Time To First Byte. This timing includes 1 round trip of latency and the time the server took to prepare the response) for this request is 3.26 seconds

      这是另一个截图,当我点击第二页时,它再次下载38 MB的内容,大约需要。 6分钟。 (server paging = true和pageSize = 10)

      enter image description here

      代码调试截图:

      enter image description here

      我做错了什么?任何人都可以帮助我。

      提前谢谢。

1 个答案:

答案 0 :(得分: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得到的?