Querying within Child Collections #
TIP
Marten V4 greatly improved Marten's abilities to query within child collections of documents
Quantifier Operations within Child Collections #
Marten supports the Any()
and Contains()
quantifier operations within child collections.
The following code sample demonstrates the supported Linq patterns for collection searching:
cs
public class ClassWithChildCollections
{
public Guid Id;
public IList<User> Users = new List<User>();
public Company[] Companies = new Company[0];
public string[] Names;
public IList<string> NameList;
public List<string> NameList2;
}
public void searching(IDocumentStore store)
{
using (var session = store.QuerySession())
{
var searchNames = new string[] { "Ben", "Luke" };
session.Query<ClassWithChildCollections>()
// Where collections of deep objects
.Where(x => x.Companies.Any(_ => _.Name == "Jeremy"))
// Where for Contains() on array of simple types
.Where(x => x.Names.Contains("Corey"))
// Where for Contains() on List<T> of simple types
.Where(x => x.NameList.Contains("Phillip"))
// Where for Contains() on IList<T> of simple types
.Where(x => x.NameList2.Contains("Jens"))
// Where for Any(element == value) on simple types
.Where(x => x.Names.Any(_ => _ == "Phillip"))
// The Contains() operator on subqueries within Any() searches
// only supports constant array of String or Guid expressions.
// Both the property being searched (Names) and the values
// being compared (searchNames) need to be arrays.
.Where(x => x.Names.Any(_ => searchNames.Contains(_)));
}
}
You can search on equality of multiple fields or properties within the child collection using the &&
operator:
cs
var results = theSession
.Query<Target>()
.Where(x => x.Children.Any(_ => _.Number == 6 && _.Double == -1))
.ToArray();
Finally, you can query for child collections that do not contain a value:
cs
theSession.Query<DocWithArrays>().Count(x => !x.Strings.Contains("c"))
.ShouldBe(2);
cs
theSession.Query<DocWithArrays>().Count(x => !x.Strings.Contains("c"))
.ShouldBe(2);
Querying within Value IEnumerables #
As of now, Marten allows you to do "contains" searches within Arrays, Lists & ILists of primitive values like string or numbers:
cs
public void query_against_string_array()
{
var doc1 = new DocWithArrays { Strings = new [] { "a", "b", "c" } };
var doc2 = new DocWithArrays { Strings = new [] { "c", "d", "e" } };
var doc3 = new DocWithArrays { Strings = new [] { "d", "e", "f" } };
theSession.Store(doc1);
theSession.Store(doc2);
theSession.Store(doc3);
theSession.SaveChanges();
theSession.Query<DocWithArrays>().Where(x => x.Strings.Contains("c")).ToArray()
.Select(x => x.Id).ShouldHaveTheSameElementsAs(doc1.Id, doc2.Id);
}
Marten also allows you to query over IEnumerables using the Any method for equality (similar to Contains):
cs
[Fact]
public void query_against_number_list_with_any()
{
var doc1 = new DocWithLists { Numbers = new List<int> { 1, 2, 3 } };
var doc2 = new DocWithLists { Numbers = new List<int> { 3, 4, 5 } };
var doc3 = new DocWithLists { Numbers = new List<int> { 5, 6, 7 } };
var doc4 = new DocWithLists { Numbers = new List<int> { } };
theSession.Store(doc1, doc2, doc3, doc4);
theSession.SaveChanges();
theSession.Query<DocWithLists>().Where(x => x.Numbers.Any(_ => _ == 3)).ToArray()
.Select(x => x.Id).ShouldHaveTheSameElementsAs(doc1.Id, doc2.Id);
// Or without any predicate
theSession.Query<DocWithLists>()
.Count(x => x.Numbers.Any()).ShouldBe(3);
}
As of 1.2, you can also query against the Count()
or Length
of a child collection with the normal comparison operators (==
, >
, >=
, etc.):
cs
[Fact]
public void query_against_number_list_with_count_method()
{
var doc1 = new DocWithLists { Numbers = new List<int> { 1, 2, 3 } };
var doc2 = new DocWithLists { Numbers = new List<int> { 3, 4, 5 } };
var doc3 = new DocWithLists { Numbers = new List<int> { 5, 6, 7, 8 } };
theSession.Store(doc1);
theSession.Store(doc2);
theSession.Store(doc3);
theSession.SaveChanges();
theSession.Query<DocWithLists>()
.Single(x => x.Numbers.Count() == 4).Id.ShouldBe(doc3.Id);
}
IsOneOf #
IsOneOf()
extension can be used to query for documents having a field or property matching one of many supplied values:
cs
// Finds all SuperUser's whose role is either
// Admin, Supervisor, or Director
var users = session.Query<SuperUser>()
.Where(x => x.Role.IsOneOf("Admin", "Supervisor", "Director"));
To find one of for an array you can use this strategy:
cs
// Finds all UserWithNicknames's whose nicknames matches either "Melinder" or "Norrland"
var nickNames = new[] {"Melinder", "Norrland"};
var users = session.Query<UserWithNicknames>()
.Where(x => x.Nicknames.IsOneOf(nickNames));
To find one of for a list you can use this strategy:
cs
// Finds all SuperUser's whose role is either
// Admin, Supervisor, or Director
var listOfRoles = new List<string> {"Admin", "Supervisor", "Director"};
var users = session.Query<SuperUser>()
.Where(x => x.Role.IsOneOf(listOfRoles));
In #
In()
extension works exactly the same as IsOneOf()
. It was introduced as syntactic sugar to ease RavenDB transition:
cs
// Finds all SuperUser's whose role is either
// Admin, Supervisor, or Director
var users = session.Query<SuperUser>()
.Where(x => x.Role.In("Admin", "Supervisor", "Director"));
To find one of for an array you can use this strategy:
cs
// Finds all UserWithNicknames's whose nicknames matches either "Melinder" or "Norrland"
var nickNames = new[] {"Melinder", "Norrland"};
var users = session.Query<UserWithNicknames>()
.Where(x => x.Nicknames.In(nickNames));
To find one of for a list you can use this strategy:
cs
// Finds all SuperUser's whose role is either
// Admin, Supervisor, or Director
var listOfRoles = new List<string> {"Admin", "Supervisor", "Director"};
var users = session.Query<SuperUser>()
.Where(x => x.Role.In(listOfRoles));
IsSupersetOf #
cs
// Finds all Posts whose Tags is superset of
// c#, json, or postgres
var posts = theSession.Query<Post>()
.Where(x => x.Tags.IsSupersetOf("c#", "json", "postgres"));
IsSubsetOf #
cs
// Finds all Posts whose Tags is subset of
// c#, json, or postgres
var posts = theSession.Query<Post>()
.Where(x => x.Tags.IsSubsetOf("c#", "json", "postgres"));