jwaliszko
jwaliszko

Reputation: 17064

Finding objects which contains at least all elements from subset using RavenDB and LINQ

I have simple type Question:

public class Question
{
    public Question(string id)
    {
        Id = id;
        Tags = new List<string>();
    }

    public string Id { get; private set; }
    public IList<string> Tags { get; set; }            
}

I have defined sample collection of such questions:

var q1 = new Question("q1") { Tags = new List<string>() {"aa", "bb"} };
var q2 = new Question("q2") { Tags = new List<string>() {"aa"} };
var q3 = new Question("q3") { Tags = new List<string>() {"aa", "bb", "cc"} };
var q4 = new Question("q4");
var questions = new List<Question>() {q1, q2, q3, q4};

Now I need to find all questions, which contains at least all tags, from given subset. Subset is defined below:

string[] tags = new[] {"aa", "bb"};

The query which I use to get desired result is:

var result = questions.Where(x => !tags.Except(x.Tags).Any()).ToList();

Result is a list of 2 questions: q1 and q3, which works properly while I'm using linq-to-objects.

Unfortunately, while I'm trying to query such questions, which are now persisted in RavenDB, I get an exception:

var result = DocumentSession.Query<Question>()
                     .Where(x => !tags.Except(x.Tags).Any()).ToList();

results in:

System.InvalidOperationException: Cannot understand how to translate value(Core.Commands.GetQuestions+<>c__DisplayClass0).tags.Except(x.Tags)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.GetPath(Expression expression, String& path, Type& memberType, Boolean& isNestedPath)
   at Raven.Client.Linq.DynamicQueryProviderProcessor`1.GetMember(Expression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitAny(MethodCallExpression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitEnumerableMethodCall(MethodCallExpression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitMethodCall(MethodCallExpression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitExpression(Expression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitExpression(Expression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitQueryableMethodCall(MethodCallExpression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitMethodCall(MethodCallExpression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.VisitExpression(Expression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.GetLuceneQueryFor(Expression expression)
   at Raven.Client.Linq.RavenQueryProviderProcessor`1.Execute(Expression expression)
   at Raven.Client.Linq.DynamicRavenQueryProvider`1.Execute(Expression expression)
   at Raven.Client.Linq.DynamicRavenQueryProvider`1.System.Linq.IQueryProvider.Execute(Expression expression)
   at Raven.Client.Linq.RavenQueryInspector`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
...

How to perform what I want using RavenDB ?

Upvotes: 4

Views: 2030

Answers (3)

Ayende Rahien
Ayende Rahien

Reputation: 22956

Jarek, what you want to do is:

var q = session.Query<Question>();
foreach(var tag in tags)
{
    var currentTag = tag;
    q = q.Where(x=>x.Tags.Any(xTag=>xTag == currentTag));
}

This will give you all the questions that have at least all those tags.

Upvotes: 6

Matt Warren
Matt Warren

Reputation: 10291

The RavenDB LINQ provider doesn't support this syntax because the underlying index mechanism (Lucene) doesn't allow this type of query.

However there is a new feature in RavenDB that will allow you to do this, see this thread for more info. Note you'll need to get the latest build to be able to use this feature.

You should be able to write your query as:

var result = session.Query<Question>("IndexName")
        .Where(x => x.Tags.Any(t => t == "aa"))
        .Intersect()
        .Where(x => x.Tags.Any(t => t == "bb")
        .ToList();

And the index needs to look something like this:

from qu in docs.Questions
from tag in qu.Tags
select new { tag }

Thanks, I've been looking for another scenario for this new feature, so when I get a chance I'll create a gist showing the full sample.

Upvotes: 0

David Anderson
David Anderson

Reputation: 13670

It looks like the LINQ Provider did not implement Except as part of the Query Pattern. If I understand your requirement enough, you might be able to use SequenceEquals.

var result = questions.Where(q => q.Tags.SequenceEqual(tags));

Using the code you provided, this returned exactly one result {"aa","bb"}. If the RavenDB Provider doesn't provide enough of the Query Pattern implementation for you, then just do it without using LINQ at all.

Upvotes: 1

Related Questions