Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bitwise enum (flags) query using MongoDB's official C# driver

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.

like image 832
Danny Varod Avatar asked Jul 01 '13 23:07

Danny Varod


2 Answers

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).

like image 61
Jamby Avatar answered Sep 29 '22 08:09

Jamby


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;
}
like image 25
i3arnon Avatar answered Sep 29 '22 08:09

i3arnon