When I try to run a LINQ query of the form:
MongoCollection<MyEntity> collection;
collection.AsQueryable().Where(entity =>
(entity.Flags & MyFlags.AFlag) != MyFlags.None);
I get an ArgumentException
with the message Unsupported where clause: ((Int32)((Int32)entity.Flags & 4) != 0).
Is this a known bug/feature?
Is there any workaround?
From the documentation it seems like MongoDB has a bitwise update, but not a bitwise query.
For comparison, the same query runs smoothly above Redis using ServiceStack as a client.
I did find these two links (link1, link2) which suggest using JavaScript, however, that would make the implementation of the service layer very dependant on the DB technology.
Starting with MongoDB v 3.2 you can use bitsAllSet or bitsAnySet depending on what are you searching for.
So, with C# MongoDB Driver:
//Check single Flag as OP
collection.Find(Builders<MyEntity>.Filter.BitsAllSet(myEntity => myEntity.Flags, (long) MyFlags.AFlag));
//Check all multiple Flags
collection.Find(Builders<MyEntity>.Filter.BitsAllSet(myEntity => myEntity.Flags, (long) MyFlags.AFlag | MyFlags.BFlag));
//Check any multiple Flag
collection.Find(Builders<MyEntity>.Filter.BitsAnySet(myEntity => myEntity.Flags, (long) MyFlags.AFlag | MyFlags.BFlag));
Note: There's an open issue to use indexes on Bitwise operations. On MongoDB 4.2 they still don't use them, so be aware if you plan on using them massively (we were and we had to refactor everything afterwards).
My solution has two parts. I made a serializer for Enum flags that stores all the values in a list of strings. I made an extension method for Linq to "inject" the mongo query i need.
public static IQueryable<TItem> HasFlags<TItem, TProperty>(
this IQueryable<TItem> items,
Expression<Func<TItem, TProperty>> itemPropertyExpression,
params Enum[] enumFlags)
{
var enumFlagNames = enumFlags.Select(enumFlag => (BsonValue)enumFlag.ToString());
return items.Where(item => Query.In(ExtendedObject.GetPropertyName(itemPropertyExpression), enumFlagNames).Inject());
}
That way, its both readable and i don't need to deserialize all the objects into memory.
P.S: The GetPropertyName method is just a type safe way to get the property name:
public static string GetPropertyName<TClass, TProperty>(
Expression<Func<TClass, TProperty>> entityPropertyExpression)
{
return ((MemberExpression)entityPropertyExpression.Body).Member.Name;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With