2

有一个已知问题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;
}

这可以编译,但是当我执行查询时,我得到:

LambdaMicrosoft.Azure.Documents.Linq.DocumentQueryException:不支持带有 NodeType 的表达式。

...因为 documentDb 无法处理 lambda 子句

我还尝试用直接调用 where 方法替换 lambda 子句,因此调用变为:

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

return updatedQueryExpression; 

...但是,这会导致:

System.ArgumentException:静态方法需要空实例,非静态方法需要非空实例

4

1 回答 1

0

首先,我们被告知我们只能使用文档的属性进行排序,而不是派生值。

所以,对我来说,我也建议你遵循@Paul 在评论中提到的建议:Querying all the matching data from cosmos db, then try to sort the result list and take the top elements.我相信你已经知道了表达式如何在 C# 中获取列表的前 N ​​个元素?

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

由于您必须获取顶级元素,并且如果您的匹配数据集足够大,无论您使用 LINQ 还是 SQL,您都会遇到可以通过Continuation Token.

于 2020-01-03T09:26:16.263 回答