Search code examples
c#realm

Dynamic query builder for .Net realm


I am using Realm database for storing data locally on my device. In the application a user can see a list of books and apply Filters/Sorting on them. A book is a Pojo object which has a Title, Author(string), Status(Enum) and Publish-Date(time stamp). The user of the application can filter books based on those four fields, there are more fields on the object but for simplicity I am only using 4 at the moment.

I am trying to create a dynamic query builder where all the selected filters will be applied on the query.

What I have seen in the documentation is that we can query realm like this

 var allBooks = realm.All<Book>().Where(book =>
        book.Title== "Star wars" ||
        book.Status== "Sold");

what I want is to construct the query on my own by passing the list of filters int the method. I was thinking of creating a wrapper class with Key/Value pair and assigning the name of the filter and the value. Then I could pass a list of those to a method. My question is how construct the query builder from a list of key/value where the key is the object field like "Title", "Status" and the value is "Star wars", "Sold".

Any help will be greatly appreciate.


Solution

  • You can create a dynamic predicate with Linq Expressions:

    public static Expression<Func<T, bool>> CreatePredicate<T>(KeyValuePair<string, string>[] filters)
    {
        var type = typeof(T);
        var parameter = Expression.Parameter(type, "t");
    
        if (filters.Length == 0)  // no filtering
            return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);
    
        Expression body = Expression.Constant(false);
        foreach (var filter in filters)
        {
            var member = Expression.PropertyOrField(parameter, filter.Key);
            var value = Expression.Constant(filter.Value);
            body = Expression.OrElse(body, Expression.Equal(member, value));
        }
    
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }
    

    Now if All<Book>() returns an IQueryable<Book> use it like:

    var predicate = CreatePredicate<Book>(new[]
    {
        new KeyValuePair<string, string>("Title", "Star wars"),
        new KeyValuePair<string, string>("Status", "Sold"),
    });
    
    var allBooks = realm.All<Book>().Where(predicate);
    

    In case if All<Book>() returns IEnumerable<Book> you still able to use it, just add call to Compile:

    var allBooks = realm.All<Book>().Where(predicate.Compile());