LINQ to CosmosDB 中数组的交集

Jam*_*yot 4 linq azure azure-cosmosdb

我正在尝试在我的数据库中查找所有项目,这些项目在数组中至少有一个值,该值与我在代码中拥有的数组中的任何值相匹配(两个数组的交集不应为空)。

基本上,我正在努力实现这一目标:

public List<Book> ListBooks(string partitionKey, List<string> categories)
{
    return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
    {
        PartitionKey = new PartitionKey(partitionKey)
    })
    .Where(b => b.Categories.Any(c => categories.Contains(c))
    .ToList();
}
Run Code Online (Sandbox Code Playgroud)

Book 类看起来像这样:

public class Book
{
    public string id {get;set;}
    public string Title {get;set;}
    public string AuthorName {get;set;}
    public List<string> Categories {get;set;}
}
Run Code Online (Sandbox Code Playgroud)

但是,SDK 会抛出一个异常,指出在执行此代码时不支持 Method 'Any'。这也不起作用:

return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
    PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
Run Code Online (Sandbox Code Playgroud)

以下代码有效,因为只有一个类别可以找到:

public List<Book> ListBooksAsync(string category)
{
    return _client.CreateDocumentQuery<Book>(GetCollectionUri())
    .Where(b => b.Categories.Contains(category))
    .ToList();
}
Run Code Online (Sandbox Code Playgroud)

在普通 SQL 中,我可以将多个队列ARRAY_CONTAINS与多个OR查询正确执行。

SELECT * FROM root 
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
   OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
   OR ARRAY_CONTAINS(root["Categories"], 'Legend')
Run Code Online (Sandbox Code Playgroud)

我试图找到使用 LINQ 实现这一目标的最佳方法,但我什至不确定这是否可能。

Jas*_*son 5

在这种情况下,我使用了一个辅助方法来组合表达式,就像在最后一个示例中一样计算为 SQL。下面的辅助方法“MakeOrExpression”允许您传递许多谓词(在您的情况下,是对 b.Categories.Contains(category) 的单独检查)并生成一个表达式,您可以将其放入 .Where(expression) 上的参数中文档查询。

class Program
{
    private class Book
    {
        public string id { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
        public List<string> Categories { get; set; }
    }

    static void Main(string[] args)
    {
        var comparison = new[] { "a", "b", "c" };

        var target = new Book[] {
            new Book { id = "book1", Categories = new List<string> { "b", "z" } },
            new Book { id = "book2", Categories = new List<string> { "s", "t" } },
            new Book { id = "book3", Categories = new List<string> { "z", "a" } } };

        var results = target.AsQueryable()
            .Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));

        foreach (var result in results)
        {
            // Should be book1 and book3
            Console.WriteLine(result.id);
        }

        Console.ReadLine();
    }

    private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
    {
        var combinedExpression = inputExpressions.Skip(1).Aggregate(
            inputExpressions[0].Body, 
            (agg, x) => Expression.OrElse(agg, x.Body));

        var parameterExpression = Expression.Parameter(typeof(T));

        var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression, 
            Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));

        var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);

        var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
        return result;
    }

    private class ReplaceParameterVisitor : ExpressionVisitor
    {
        private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
        private readonly ParameterExpression parameterExpression;

        public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
        {
            this.parameterExpression = parameterExpressionParam;
            this.targetParameterExpressions = targetParameterExpressionsParam;
        }

        public override Expression Visit(Expression node)
            => targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
    }
}
Run Code Online (Sandbox Code Playgroud)