Creating Dynamic LINQ Queries in C# with Predicate Builder

By FoxLearn 2/22/2025 3:53:06 AM   2
In C#, one of the most powerful techniques for building dynamic LINQ queries is using the Predicate Builder.

It allows you to construct flexible queries at runtime based on user input or varying conditions, making it perfect for scenarios such as filtering data in web applications or querying large datasets.

In this tutorial, we will explore how to use the Predicate Builder to create dynamic LINQ queries in C#.

What is Predicate Builder?

The Predicate Builder is a LINQ extension that helps you dynamically combine multiple conditions into a single expression. This allows you to build complex queries without needing to manually write numerous if-else conditions.

The primary advantage of Predicate Builder is its ability to easily combine multiple search filters into a single dynamic query. You can use it to create SQL-like queries dynamically and efficiently.

Let’s take an example of a healthcare system where we need to query a list of patients based on several filter parameters.

Step 1: Define the Patient Model

First, we’ll define the PatientInfo class, which will represent our data model.

public class PatientInfo
{
    public int PatientID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Nullable<DateTime> BirthDate { get; set; }
    public string Gender { get; set; }
    public string PatientType { get; set; }
    public string InsuranceNumber { get; set; }
    public Nullable<DateTime> AdmissionDate { get; set; }
    public bool IsHaveInsurance { get; set; }
}

Step 2: Building the Dynamic Query with Predicate Builder

We can now build a method to filter patients dynamically based on input parameters. We will use PredicateBuilder to create a query with dynamic conditions.

public ActionResult SearchPatients(string firstName, string lastName, string gender, string patientType, string birthDate)
{
    // Initialize the predicate to always return true
    var predicate = PredicateBuilder.True<PatientInfo>();

    // Add conditions to the predicate based on provided filters
    if (!string.IsNullOrEmpty(firstName))
    {
        predicate = predicate.And(p => p.FirstName.Contains(firstName));
    }

    if (!string.IsNullOrEmpty(lastName))
    {
        predicate = predicate.And(p => p.LastName.Contains(lastName));
    }

    if (!string.IsNullOrEmpty(gender))
    {
        predicate = predicate.And(p => p.Gender.Equals(gender));
    }

    if (!string.IsNullOrEmpty(patientType))
    {
        predicate = predicate.And(p => p.PatientType.Equals(patientType));
    }

    if (!string.IsNullOrEmpty(birthDate))
    {
        DateTime dob;
        if (DateTime.TryParse(birthDate, out dob))
        {
            predicate = predicate.And(p => p.BirthDate.Value.Date == dob.Date);
        }
    }

    // Query the database using the dynamic predicate
    var filteredPatients = db.Patients.Where(predicate).ToList();

    // Return the filtered patients to the view
    return View(filteredPatients);
}

In this example:

  • Predicate Initialization: We start with PredicateBuilder.True<PatientInfo>(), which creates a predicate that always evaluates to true. This serves as the base condition for our query.
  • Dynamic Conditions: Each condition (e.g., first name, gender, etc.) is appended to the predicate using And(). This allows us to build a query that can handle multiple filters.
  • Query Execution: Finally, the dynamically constructed predicate is applied to the LINQ query (db.Patients.Where(predicate)) to fetch the filtered results.

To understand how Predicate Builder works, here’s the core helper class that provides methods to create and combine predicates.

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return param => true; }
    public static Expression<Func<T, bool>> False<T>() { return param => false; }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.AndAlso);
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.OrElse);
    }

    private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
    {
        var map = first.Parameters
            .Select((f, i) => new { f, s = second.Parameters[i] })
            .ToDictionary(p => p.s, p => p.f);

        var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }

    private class ParameterRebinder : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;
        private ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }
    }
}

Benefits of Using Predicate Builder

  • Dynamic Queries: You can create dynamic queries based on user input, allowing for flexible search functionality.
  • Clean Code: By using Predicate Builder, you avoid writing multiple if conditions and manually combining filter criteria.
  • Performance: Predicate Builder helps you optimize the code by combining conditions efficiently, avoiding unnecessary database queries.

The Predicate Builder is a powerful tool in C# that allows you to construct dynamic LINQ queries with ease. By using it, you can dynamically filter data based on various parameters at runtime, greatly simplifying the complexity of handling multiple filter conditions.