DEV Community

Karim Fahmy
Karim Fahmy

Posted on • Edited on

2

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 😉

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay