首页 文章

LINQ中的数组与CosmosDB的交集

提问于
浏览
2

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

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

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();
}

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;}
}

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

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

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

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

在纯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')

我正试图找到用LINQ实现这一目标的最佳方法,但我甚至不确定它是否可行 .

1 回答

  • 4

    在这种情况下,我使用了一个帮助器方法来组合表达式,其方式与最终示例中的SQL一样 . 下面的辅助方法'MakeOrExpression'允许您传递一些谓词(在您的情况下,单独检查b.Categories.Contains(类别))并生成一个表达式,您可以将参数放入.Where(表达式)中文件查询 .

    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);
        }
    }
    

相关问题