[英]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.