DEV Community

Karim Fahmy
Karim Fahmy

Posted on • Edited on

build dynamic query predicate in Linq

I've created a nuget package Linq.Extension.PredicateBuilder helps you to solve complexity in filter collection by apply filtering on combination of properties.

Overview

To make the scenario concrete, let's assume that our object is declared as follows:

public class Post
  {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public bool IsPublished { get; set; }
        public DateTime PublishedAt { get; set; }
        public int TotalViews { get; set; }
        public int TotalComments { get; set; }
  }
Enter fullscreen mode Exit fullscreen mode

Now suppose we have a collection of Posts like this (this is just for explanation purposes, in real life you would get a much bigger collection from database

var posts = new List<Post>
            {
                new Post {Id = 1, Name = "post1" , Description = "post1 description", IsPublished = true, PublishedAt = DateTime.Now, TotalViews = 50, TotalComments = 4},
                new Post {Id = 2, Name = "post2" , Description = "post2 description", IsPublished = false, PublishedAt = DateTime.Now.AddDays(-3), TotalViews = 10, TotalComments = 1},
                new Post {Id = 3, Name = "post3" , Description = "post3 description", IsPublished = true, PublishedAt = DateTime.Now.AddDays(-8), TotalViews = 120, TotalComments = 8},
                new Post {Id = 4, Name = "post4" , Description = "post4 description", IsPublished = true, PublishedAt = DateTime.Now.AddDays(-1), TotalViews = 40, TotalComments = 5},
                new Post {Id = 5, Name = "post5" , Description = "post5 description", IsPublished = true, PublishedAt = DateTime.Now, TotalViews = 0, TotalComments = 0},
                new Post {Id = 6, Name = "post6" , Description = "post6 description", IsPublished = true, PublishedAt = DateTime.Now.AddDays(-10), TotalViews = 150, TotalComments = 10},
                new Post {Id = 7, Name = "post7" , Description = "post7 description", IsPublished = false, PublishedAt = DateTime.Now, TotalViews = 0, TotalComments = 0},
                new Post {Id = 8, Name = "post8" , Description = "post8 description", IsPublished = true, PublishedAt = DateTime.Now.AddDays(-20), TotalViews = 250, TotalComments = 15},

            };
Enter fullscreen mode Exit fullscreen mode

Suppose we want to allow the user to filter the collection on any property or any combination of properties, one way would be to have a function for each property and each combination of properties, something like:

public IList<Post> FilterByName(string  name)
{
    return  posts.Where(p => p.Name == name).ToList();
}

public  IList<posts> FilterByDescription(string  description)
{
    return  posts.Where(p => p.Description.Contains(description)).ToList();
}

public  IList<posts> FilterByTotalViewsGreaterOrEqual(int totalViews)
{
    return  posts.Where(p => p.TotalViews >= totalViews).ToList();
}
Enter fullscreen mode Exit fullscreen mode

As you can see, this becomes a very tedious job since the number of functions to cover all possible combinations is quite big.

The other way to filter the collection, which is much more convenient and tidier is to build an expression tree dynamically and pass it to the where clause for filtering so Linq.Extension.PredicateBuilder comes

Installation

dotnet add package Linq.Extension.PredicateBuilder

Usage

using Linq.Extension.PredicateBuilder;

internal class Program
    {
        static void Main(string[] args)
        {
            var predicate = new PostViewComponentModel
            {
                Search = new List<Search>
                {
                    new Search
                    {
                        Field = new Field{ Value = "PublishedAt" },
                        Operator = new Operator { Value = OperatorComparer.Between},
                        Value = new Value{ value =  "07/08/2023" , value2 = "07/18/2023"}
                    },
                    new Search
                    {
                        Field = new Field{ Value = "Name" },
                        Operator = new Operator { Value = OperatorComparer.BeginsWith },
                        Value = new Value{ value = "p" }
                    },
                    new Search
                    {
                        Field = new Field{ Value = "Status" },
                        Operator = new Operator { Value = OperatorComparer.In },
                        Value = new Value{ value = "0,2" }
                    }
                },
                PageNumber = 1,
                PageSize = 20

            }; 
            var result = new Post().GetPosts().BuildQuery(predicate.PredicateBuilderFilterRule
                , new PredicateBuilderOptions() { CultureInfo = CultureInfo.CurrentCulture }).ToPaged(predicate);
        }
    }

    public enum Status
    {
        New,
        Pending,
        Completed
    }


    public class PostViewComponentModel : PredicateBuilderInvokerBase<Post>
    {
    }

    public class Post
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public bool IsPublished { get; set; }
        public DateTime PublishedAt { get; set; }
        public int TotalViews { get; set; }
        public int TotalComments { get; set; }
        public Status Status { get; set; }

        public IQueryable<Post> GetPosts()
        {
            var posts = new List<Post>
            {
                new Post {Id = 1, Name = "POST1" , Description = "post1 description", IsPublished = true, 
                    PublishedAt = DateTime.Now.AddMinutes(-3), TotalViews = 50, TotalComments = 4 , Status = 0},

                new Post {Id = 2, Name = "POST2" , Description = "post2 description", IsPublished = false,
                    PublishedAt = DateTime.Now.AddDays(-1), TotalViews = 10, TotalComments = 1 , Status = (Status)1},

                new Post {Id = 3, Name = "POST3" , Description = "post3 description", IsPublished = true,
                    PublishedAt = DateTime.Now.AddDays(-8), TotalViews = 120, TotalComments = 8, Status = (Status)2},

                new Post {Id = 4, Name = "POST4" , Description = "post4 description", IsPublished = true,
                    PublishedAt = DateTime.Now.AddDays(-1), TotalViews = 40, TotalComments = 5, Status = (Status)1},

                new Post {Id = 5, Name = "POST5" , Description = "post5 description", IsPublished = true,
                    PublishedAt = DateTime.Now, TotalViews = 0, TotalComments = 0 , Status = (Status)2},

                new Post {Id = 6, Name = "POST6" , Description = "post6 description", IsPublished = true,
                    PublishedAt = DateTime.Now.AddDays(-10), TotalViews = 150, TotalComments = 10, Status = (Status)2},

                new Post {Id = 7, Name = "POST7" , Description = "post7 description", IsPublished = false,
                    PublishedAt = DateTime.Now, TotalViews = 0, TotalComments = 0, Status = (Status)2},

                new Post {Id = 8, Name = "POST8" , Description = "post8 description", IsPublished = true,
                    PublishedAt = DateTime.Now.AddDays(-20), TotalViews = 250, TotalComments = 15, Status = 0},

            };

            return posts.AsQueryable();
        }
    }

Enter fullscreen mode Exit fullscreen mode

That set #HappyCoding 😉

Top comments (0)