为什么 Kendo 的 ToDataSourceResult 为一个查询创建两个 ADO.Net 调用

Why is Kendo's ToDataSourceResult creating two ADO.Net Calls for one query

我一直在分析与 Kendo 网格小部件一起使用的 MVC 控制器上的读取操作方法,并注意到 Entity Framework 及其查询有一些奇怪的行为。当我直接在 IQueryable 上使用 ToDataSourceResult() 时,EF 上下文会生成一个选择 1(即什么都不做)但包括所有连接子句和过滤器的查询。之后 returns 它会按预期发送实际查询,并应用过滤器和分页等。

注意:这并没有破坏任何东西,我只是在尝试优化,因为我在网格中使用 ServerOperation(true) 和 AJAX,这可能会导致大量调用读取操作和很多这些重复的 ADO.Net 查询(也是因为虚拟调用包含连接子句,对于大型数据集,它实际上可能 运行 慢)。

发生这种情况是否有原因?有没有办法在没有额外调用的情况下仍然对数据库进行服务器分页和过滤(例如 OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY 来自下面的结果,而不是首先使用 linq-to-objects 枚举数据库)?

EntityFramework : v6.2
.Net 框架:v4.7.2
ASP.NetMVC 5
Kendo 2019.1.220
MS SQL 数据库

(缩短)获取重复调用的代码:

    public ActionResult Read([DataSourceRequest]DataSourceRequest request)
    {          
            using (var BP01DB = new BP01DBContext())
            {
                BP01DB.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);

                var records = (from customer in BP01DB.CUSTOMER_2007
                               join group2012 in BP01DB.GROUP_2012 on customer.GROUP_CUST_KEY equals group2012.GROUP_CALC_KEY_2012
                               join cobrCust in BP01DB.COBRCUSTJ_2006 on customer.CUST_CALC_KEY_2007 equals cobrCust.CUST_COBR_KEY
                               select new InquireCustBranchSalesRecord
                               {
                                   CobrCustDbKey = cobrCust.COBRCUSTJ_2006_DBKEY,
                               });


                return Json(records.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);

            }
    }

示例输出:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM   [dbo].[CUSTOMER_2007] AS [Extent1]
    INNER JOIN [dbo].[GROUP_2012] AS [Extent2] ON [Extent1].[GROUP_CUST_KEY] = [Extent2].[GROUP_CALC_KEY_2012]
    INNER JOIN [dbo].[COBRCUSTJ_2006] AS [Extent3] ON [Extent1].[CUST_CALC_KEY_2007] = [Extent3].[CUST_COBR_KEY]
)  AS [GroupBy1]


-- Executing at 6/11/2020 4:28:20 PM -05:00

-- Completed in 105 ms with result: SqlDataReader



Closed connection at 6/11/2020 4:28:20 PM -05:00

Opened connection at 6/11/2020 4:28:20 PM -05:00

SELECT 
    [Extent3].[COBRCUSTJ_2006_DBKEY] AS [COBRCUSTJ_2006_DBKEY]
    FROM   [dbo].[CUSTOMER_2007] AS [Extent1]
    INNER JOIN [dbo].[GROUP_2012] AS [Extent2] ON [Extent1].[GROUP_CUST_KEY] = [Extent2][GROUP_CALC_KEY_2012]
    INNER JOIN [dbo].[COBRCUSTJ_2006] AS [Extent3] ON [Extent1].[CUST_CALC_KEY_2007] = [Extent3]. [CUST_COBR_KEY]
    ORDER BY row_number() OVER (ORDER BY [Extent3].[COBRCUSTJ_2006_DBKEY] ASC)
    OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY 


-- Executing at 6/11/2020 4:28:20 PM -05:00

-- Completed in 88 ms with result: SqlDataReader



Closed connection at 6/11/2020 4:28:21 PM -05:00

现在我要做的就是在 ToDataSourceResult() 执行之前枚举 IQueryable(在本例中添加 .ToList()),这是适用于小数据集的客户端分页过滤,但这里不行。

var records = (from customer in BP01DB.CUSTOMER_2007
                               join group2012 in BP01DB.GROUP_2012 on customer.GROUP_CUST_KEY equals group2012.GROUP_CALC_KEY_2012
                               join cobrCust in BP01DB.COBRCUSTJ_2006 on customer.CUST_CALC_KEY_2007 equals cobrCust.CUST_COBR_KEY
                               select new InquireCustBranchSalesRecord
                               {
                                   CobrCustDbKey = cobrCust.COBRCUSTJ_2006_DBKEY,
                               }).ToList();

并且输出:

Opened connection at 6/11/2020 4:37:24 PM -05:00

SELECT 
[Extent3].[COBRCUSTJ_2006_DBKEY] AS [COBRCUSTJ_2006_DBKEY]
FROM   [dbo].[CUSTOMER_2007] AS [Extent1]
INNER JOIN [dbo].[GROUP_2012] AS [Extent2] ON [Extent1].[GROUP_CUST_KEY] = [Extent2].[GROUP_CALC_KEY_2012]
INNER JOIN [dbo].[COBRCUSTJ_2006] AS [Extent3] ON [Extent1].[CUST_CALC_KEY_2007] = [Extent3].[CUST_COBR_KEY]


-- Executing at 6/11/2020 4:37:25 PM -05:00

-- Completed in 129 ms with result: SqlDataReader



Closed connection at 6/11/2020 4:37:25 PM -05:00

这个查询

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM   [dbo].[CUSTOMER_2007] AS [Extent1]
    INNER JOIN [dbo].[GROUP_2012] AS [Extent2] ON [Extent1].[GROUP_CUST_KEY] = [Extent2].[GROUP_CALC_KEY_2012]
    INNER JOIN [dbo].[COBRCUSTJ_2006] AS [Extent3] ON [Extent1].[CUST_CALC_KEY_2007] = [Extent3].[CUST_COBR_KEY]
)  AS [GroupBy1]

计算行数。 UI 小部件显然会显示结果总数以及第一页。

您可以创建一个存储过程并传递 @Take@Skip 参数,然后在 SQL Server

中进行分页
SELECT *
FROM Sales.SalesOrderHeader 
ORDER BY OrderDate
OFFSET (@Skip) ROWS FETCH NEXT (@Take) ROWS ONLY