簡體   English   中英

用 LINQ 查詢中的 Where 替換 OrderBy

[英]Replace OrderBy with Where in LINQ query

CosmosDb有一個已知問題,如果您使用ORDER BY子句,它會排除未定義此屬性的文檔

為了解決這個問題,我試圖創建一個接受 LINQ 查詢的功能,並將Order子句替換為檢查未定義屬性的文檔,以便我們可以運行兩個查詢並合並結果。

所以:

ordersDb.Where(x => x.Name == customerName).OrderBy(x => x.CompanyName)

會成為:

ordersDb.Where(x => x.Name == customerName)
.Where(x => !x.CompanyName.IsDefined()) // IsDefined is a built in CosmosDb function

使用 Expression Builder 我創建了以下內容。 但是,我在嘗試將我的表達式稱為 Where 方法時遇到問題 - :

private sealed class OrderByToIsNotDefinedVisitor : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.DeclaringType == typeof(Queryable) &&
            (node.Method.Name == "OrderBy" || node.Method.Name == "OrderByDescending"))
        {
            // Get the IsDefined method
            var methodIsDefined = typeof(TypeCheckFunctionsExtensions).GetMethod("IsDefined",
                BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null,
                new Type[] { typeof(object) }, null);

            // Apply the IsDefined method to the property that was being used for OrderBy
            var isDefinedItem = Expression.Call(methodIsDefined, node.Arguments[1]);

            // Alter the expression to check for !IsDefined()
            var isNotDefinedItem = Expression.Not(isDefinedItem);

            var entityType = node.Method.GetGenericArguments()[0];

            var genericWhere = BuildGenericWhere();

            var methodWhere = genericWhere.MakeGenericMethod(entityType);

            var param = Expression.Parameter(entityType);

            Expression newExpression =
                Expression.Call(
                    methodWhere,
                    node.Arguments[0],
                    Expression.Lambda(
                        typeof(Func<,>).MakeGenericType(entityType, typeof(bool)),
                        isNotDefinedItem,
                        param));

            return newExpression;
        }

        return base.VisitMethodCall(node);
    }
}

private static MethodInfo BuildGenericWhere()
{
    var genericWhereMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
        .Where(x => x.Name == "Where" && x.GetGenericArguments().Length == 1)
        .Select(x => new { Method = x, Parameters = x.GetParameters() })
        .Where(x => x.Parameters.Length == 2 &&
                    x.Parameters[0].ParameterType.IsGenericType &&
                    x.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>) &&
                    x.Parameters[1].ParameterType.IsGenericType &&
                    x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
        .Select(x => x.Method)
        .Single();

    return genericWhereMethod;
}

這編譯正常,但是當我執行查詢時,我得到:

Microsoft.Azure.Documents.Linq.DocumentQueryException:不支持 NodeType Lambda表達式。

...因為 documentDb 無法處理 lambda 子句

我還嘗試用直接調用 where 方法替換 lambda 子句,因此調用變為:

var updatedQueryExpression = Expression.Call(node.Arguments[0], methodWhere, isNotDefinedItem);

return updatedQueryExpression; 

...然而,這導致:

System.ArgumentException:靜態方法需要空實例,非靜態方法需要非空實例

首先,我們被告知只能按文檔的屬性排序,不能按派生值排序。

所以,對我來說,我也建議你遵循@Paul 在評論中提到的建議:從 cosmos db 查詢所有匹配的數據,然后嘗試對結果列表進行排序並取頂部元素。我相信你已經知道了表達式如何在 C# 中獲取列表的前 N ​​個元素?

var firstFiveArrivals = myList.OrderBy(i => i.ArrivalTime).Take(5);

由於您必須獲取頂級元素,並且如果您的匹配數據集足夠大,無論您使用 LINQ 還是 SQL,您都會遇到可以通過Continuation Token解決的閾值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM