LiteDB
LiteDB copied to clipboard
[BUG] Any/All requires simple parameter on left side
Version 5.0.17
I know, it's a known issue, but there's something fishy:
Here's my models
class PhoneBookCategory
{
public Guid Id { get; set; }
[BsonRef("PhoneBook")]
public List<PhoneBook> PhoneBooks { get; set; }
}
class PhoneBook
{
public Guid Id { get; set; }
}
And here's how I'm trying to query:
List<Guid> ids = [Guid.NewGuid()];
var set = GetCollection<PhoneBookCategory>().Query().Where(x => x.PhoneBooks.Select(x => x.Id).Any(x => ids.Contains(x)))
And it throws. But... I have a simple condition, no? x.PhoneBooks.Select(x => x.Id).Any(x => ids.Contains(x)), that's not performing an any over a full collection, only a list of Ids.
The exception message being x.Customers.Select(c => c.Name).Any(n => n.StartsWith('J')) to me it seems this is the same thing, no?
Could it be an issue of subdocuments versus DbRefs? and if so, isn't the exception misleading in this case?
Also.. I know how to rewrite the above query - but what if I were to query a more complex variety:
var set = GetCollection<PhoneBookCategory>().Query().Where(x => x.PhoneBooks == null || x.PhoneBooks.Count == 0 || x.PhoneBooks.Select(x => x.Id).Any(x => ids.Contains(x)))
Is there a way to get the BsonExpression out of my two conditions so I can use the Query class to combine them together?
Is there an update on this thingy? Any workaround? My very first query banged on this error and I wonder whether this case is so rare or we are doing something odd?
On LiteDB 5.0.21
Fails:
collection.Find(p => p.Nations!.Select(n => n.CountryIso2Code).Any(isoCode => "A" == "A"))
Works (but you sort of give up embedding objects):
collection.Find(p => p.NationIsoCodes!.Any(s => s == iso2Code))
I ended up writing my own helper extension methods - one working with DbRefs, one working with subdocuments:
internal static ILiteQueryable<T> FilterReferencedSubtableContains<T>(ILiteQueryable<T> query, string subtableProperty, List<string> ids)
{
return query.Where(ReferencedTableContainsExpression(subtableProperty, ids));
}
private static ILiteQueryable<T> FilterIncludedSubtableContains<T>(ILiteQueryable<T> query, string subtableProperty, List<string> ids)
{
return query.Where(IncludedSubtableTableContainsExpression(subtableProperty, ids));
}
/// <summary>
/// use to filter referenced objects (they have the referenced id as $id)
/// </summary>
/// <param name="listProperty"></param>
/// <param name="ids"></param>
/// <returns></returns>
internal static BsonExpression ReferencedTableContainsExpression(string listProperty, List<string> ids)
{
return Query.In($"$.{listProperty}[*].$id ANY", GetArray(ids));
}
internal static BsonExpression ReferencedTableContainsOrIsEmpty(string listProperty, List<string> ids)
{
return Query.Or(ReferencedTableContainsExpression(listProperty, ids), ReferencedTableIsEmpty(listProperty));
}
internal static BsonExpression ReferencedTableIsEmpty(string listProperty)
{
return BsonExpression.Create($"COUNT($.{listProperty}[*]) = 0");
}
/// <summary>
/// use to filter by fully included objects (they have the id as _id as any top level object)
/// </summary>
/// <param name="listProperty"></param>
/// <param name="ids"></param>
/// <returns></returns>
internal static BsonExpression IncludedSubtableTableContainsExpression(string listProperty, List<string> ids)
{
return Query.In($"$.{listProperty}[*]._id ANY", GetArray(ids));
}
internal static BsonArray GetArray(List<string> ids)
{
var bsArray = new BsonArray();
foreach (var id in ids)
bsArray.Add(id);
return bsArray;
}
FilterReferencedSubtableContains is when working with DbRefs, FilterIncludedSubtableContains for included subdocuments. These methods are for searches on the id parameter, but you could easily adapt the top level methods and add the property to search on as an additional parameter.
I basically got the queries working in LiteDbStudio, then wrote the code using the Query class. It means you have to deviate from the linq only approach :(