C# 将OrderBy替换为LINQ查询中的Where
C# 将OrderBy替换为LINQ查询中的Where,c#,linq,azure-cosmosdb,expressionbuilder,C#,Linq,Azure Cosmosdb,Expressionbuilder,CosmosDb存在一个已知问题,如果使用orderby子句,它将排除未定义此属性的文档 为了解决这个问题,我正在尝试创建一个功能,该功能接受LINQ查询,并用检查未定义属性的文档来替换Order子句,这样我们就可以运行两个查询并合并结果 因此: 将成为: ordersDb.Where(x => x.Name == customerName) .Where(x => !x.CompanyName.IsDefined()) // IsDefined is a built in Cosm
CosmosDb
存在一个已知问题,如果使用orderby
子句,它将排除未定义此属性的文档
为了解决这个问题,我正在尝试创建一个功能,该功能接受LINQ查询,并用检查未定义属性的文档来替换Order
子句,这样我们就可以运行两个查询并合并结果
因此:
将成为:
ordersDb.Where(x => x.Name == customerName)
.Where(x => !x.CompanyName.IsDefined()) // IsDefined is a built in CosmosDb function
使用ExpressionBuilder,我创建了以下内容。但是,我在尝试将表达式作为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;
}
……然而,这导致:
System.ArgumentException:静态方法需要空实例,
非静态方法需要非空实例
首先,我们认为我们只能使用文档的属性进行排序,而不能使用派生值
因此,对于我来说,我也建议您遵循@Paul在评论中提到的建议:从cosmos db查询所有匹配的数据,然后尝试对结果列表进行排序并获取最上面的元素。我相信您已经知道表达式:
因为您必须获取最上面的元素,并且如果匹配的数据集足够大,那么无论您使用LINQ还是SQL,都会遇到阈值,可以通过
延续令牌
来解决 Try:ordersDb.Select(x=>x).OrderBy(x=>x.CompanyName)您可能已经考虑过这一点,但是您可以通过从查询中删除OrderBy并在客户端对结果进行排序来实现您的目标。这将允许您通过对数据库的一次调用而不是两次调用来执行操作。好建议@Paul,在前端进行排序将可行,但我们实际上只想返回前10行,这样做可能会涉及将大量额外数据带到客户端。嗨,这里有任何更新吗?我的回答对您有帮助吗?
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;
}
var updatedQueryExpression = Expression.Call(node.Arguments[0], methodWhere, isNotDefinedItem);
return updatedQueryExpression;
var firstFiveArrivals = myList.OrderBy(i => i.ArrivalTime).Take(5);