Entity Framework 以及 Top 和 OrderBy 的糟糕 OData 性能

Bad OData Performance with Entity Framework and Top and OrderBy

我正在使用 OData 5.8.0 和 EntityFramework 6.1.3,查询:

&$filter=fieldA eq 'ABCDEFG'&$skip=0&$top=10&$orderby=fieldB desc

结果:

SELECT TOP (10) 
    [Project1].[FieldA] AS [FieldA], 
    [Project1].[FieldB] AS [FieldB], 
    FROM ( SELECT [Project1].[FieldA] AS [FieldA], [Project1].[FieldB] AS [FieldB], row_number() OVER (ORDER BY [Project1].[FieldB] DESC, [Project1].[FieldA] ASC) AS [row_number]
        FROM ( SELECT 
            [Extent1].[FieldA] AS [FieldA], 
            [Extent1].[FieldB] AS [FieldB], 
            FROM [dbo].[table] AS [Extent1]
            WHERE ([Extent1].[FieldA] = 'ABCDEFG') OR (([Extent1].[FieldA] IS NULL) AND ('ABCDEFG' IS NULL))
        )  AS [Project1]
    )  AS [Project1]
    WHERE [Project1].[row_number] > 0
    ORDER BY [Project1].[FieldB] DESC, [Project1].[FieldA] ASC

对于大量字段 A,运行 需要约 20 秒才能对数据库进行处理。

如果我使用相同的 LINQ:

var newList = table.Where(f => f.fieldA == 'ABCDEFG').OrderByDescending(f => f.fieldB).Take(10).Skip(0).ToList();

结果是:

SELECT 
    [Limit1].[FieldA] AS [FieldA], 
    [Limit1].[FieldB] AS [FieldB]
    FROM ( SELECT [Limit1].[FieldA] AS [FieldA], [Limit1].[FieldB] AS [FieldB], row_number() OVER (ORDER BY [Limit1].[FieldB] DESC) AS [row_number]
        FROM ( SELECT TOP (10) [Project1].[FieldA] AS [FieldA], [Project1].[FieldB] AS [FieldB]
            FROM ( SELECT 
                [Extent1].[FieldA] AS [FieldA], 
                [Extent1].[FieldB] AS [FieldB]
                FROM [dbo].[table] AS [Extent1]
                WHERE ([Extent1].[FieldA] = 'ABCDEFG') OR (([Extent1].[FieldA] IS NULL) AND ('ABCDEFG' IS NULL))
            )  AS [Project1]
            ORDER BY [Project1].[FieldB] DESC
        )  AS [Limit1]
    )  AS [Limit1]
    WHERE [Limit1].[row_number] > 0
    ORDER BY [Limit1].[FieldB] DES

运行 需要 120 毫秒。

如何强制 OData 使用相同的表达式(即不在外部语句中使用 TOP)?

我发现这个问题是 OData 不是很智能,并且以错误的顺序应用查询选项。下面的代码首先应用 orderBy,然后是 top:

private static IQueryable<Item> ApplyOptimizedOdataOptions(IQueryable<Item> origQuery,  ODataQueryOptions<Item> options)
{
    var defaultOdataQuerySettings = new ODataQuerySettings();
    if (options.Top != null && options.OrderBy != null)
    {
        // We can optimze this query. Apply the OrderBy first, then Top.
        IQueryable results = options.OrderBy.ApplyTo(origQuery, defaultOdataQuerySettings);
        results = options.Top.ApplyTo(results, defaultOdataQuerySettings);
        results = options.ApplyTo(results, defaultOdataQuerySettings, AllowedQueryOptions.Top | AllowedQueryOptions.OrderBy);

        return results as IQueryable<Item>;
    }

    return options.ApplyTo(origQuery, defaultOdataQuerySettings) as IQueryable<Item>;
}

如果我 运行 使用此 IQueryable 生成的结果 SQL 语句:

SET STATISTICS TIME ON;
// Run SQL here
SET STATISTICS TIME OFF;

重新排序这些语句导致:

SQL Server Execution Times: CPU time = 0 ms, elapsed time = 1 ms.

与我不重新订购相比:

SQL Server Execution Times: CPU time = 1213 ms, elapsed time = 20112ms.

速度提升约 20,000 倍。

其实orderby应该早于top应用,这样top才稳定,做场景,跟你运行sql一样的逻辑,总要有一个orderby跟top,不然就在默认顺序,在你的场景中,你应该只使用top,得到结果后再排序。