【发布时间】:2020-04-20 10:44:15
【问题描述】:
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
使用表达式生成器,我创建了以下内容。但是,我在尝试将我的表达式称为 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:静态方法需要空实例, 非静态方法需要非空实例
【问题讨论】:
-
试试:ordersDb.Select(x => x).OrderBy(x => x.CompanyName)
-
您可能已经考虑过这一点,但您可以通过从查询中删除 ORDER BY 并在客户端对结果进行排序来实现您的目标。这将允许您通过一次调用数据库而不是两次来执行操作。
-
好建议@Paul,在前端订购会起作用,但我们实际上只想带回前 10 行,这样做可能会为客户端带来大量额外数据
-
您好,这里有更新吗?我的回答对您有帮助吗?
标签: c# linq azure-cosmosdb expressionbuilder