Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Visual Expression Builder for Dynamic Linq

4.73/5 (14 votes)
2 Feb 2012CPOL4 min read 120.8K   2.5K  
Let your users create their own Linq filters with this MVVM WPF tool you can include in your application

Updated! The source code has been updated to make the dynamic types more robust!

Table of Contents

ScreenShot.png

Introduction

Linq is a great way to declaratively filter and query data in in a Type-Safe, Intuitive, and very expressive way. However, your users should not have to call you every time they have a new way to filter or query their data.

So here is a way for your users to filter their data no matter where that data comes from. Whether its Linq2SQL or an in-memory structure, now your users can have the power.

It is the most valuable feature that you can add for your users in 4 lines of code.
I would also recommend actually reading the code. The entire project has only 233 lines of code in it - that's including the XAML.

Using the Code

The real functionality is in the two classes in the ViewModel. The UserControl is just a glorified DataTemplate for the ExpressionList.

ClassDiagram.png

I know that most of you will want to know how this bad boy works, so you can use the same techniques in your own solutions. However, you could just use the UserControl as-is by adding it straight into your XAML, and binding it to an ExpressionList, so that is what I will start with.

XML
<uc:FilterUserControl DataContext="{Binding ExpressionList1}" />

Somewhere in your ViewModel:

C#
public     ExpressionList ExpressionList1    { get; set; } 
ExpressionList1 = new ExpressionList() {Type=typeof(Contact)} ; 

Then once your user selected all of the filters, you get your final query like this:

C#
var filteredContacts =
   contacts.Where(ExpressionList1.GetCompleteExpression<Contact>());

Background

I first tried many other Dynamic Linq solutions. There is the "DynamicLinq" project explained in ScottGu's Blog which allows you to parse expressions in strings. So your users could write their own expressions in a text box which you could then apply to your Linq. I learned a lot about expressions from their implementation.

Then, there is the solution I almost used - The Dynamic Linq tool from the VB tool. It did almost exactly what I needed it to do, and you will notice that it looks a lot like the tool that I ended up creating which I am showing here today. The biggest problem is that it was written for WinForms and the functionality was too interwoven with the presentation. I actually needed one of the main benefits of MVVM (previously MVP) - The ability to persist the state even when the UI disappears and easily create default and saved sets of filters.

What Exactly is Happening?

As I said before, the real functionality is in the two classes in the ViewModel (I considered the builtin Expression class to be my model):

  • ExpressionVM contains the necessary data and functionality to create an expression with one comparison.
  • ExpressionList is an observable collection of ExpressionVMs with one property to create a lambda expression out of all the child expressions.

ExpressionList is Just an ObservableCollection<ExpressionVM> with two important properties:

  • Type Type - which is used in making the expression and used to populate the AvailableProperties in the ExpressionVMs.
  • Expression CompleteExpression - which combines the Expressions from all of the ExpressionVMs into one lambda expression.

ExpressionVM's members are:

  • Type ObjectType - The type being filtered (usually set from the ExpressionList)
  • PropertyInfo PropertyInfo - The information about the property that the user chose to compare on this line.
  • string PropertyName - (readonly) comes from the PropertyInfo.
  • Type PropertyType - (readonly) comes from the PropertyInfo.
  • CombineOperator - chosen by the user to determine how to combine this line with the previous line
  • CompareOperator - chosen by the user to determine how to compare the property with the constant
  • object Value - The constant to be used in the comparison
  • AvailableCombineOperators, AvailableCompareOperators, AvailableProperties - lookup list to populate the ComboBoxes.
  • GetSupportedTypes - So that we don't give the user the option to filter by image :)
  • MakeExpression -Creates an expression based on the choices of the user

The Meat (or Tofu) and Potatoes of the Solution

The most interesting code is contained in two functions MakeExpression and the getter for CompleteExpression.

C#
public LambdaExpression MakeExpression(ParameterExpression paramExpr = null)
{
    if(paramExpr == null) paramExpr = Expression.Parameter(ObjectType, "left");
            
    var callExpr = Expression.MakeMemberAccess(paramExpr, PropertyInfo);
    var valueExpr = Expression.Constant(Value, PropertyType);
    var expr = Expression.MakeBinary((ExpressionType)CompareOperator, 
        callExpr, valueExpr);
            
    return Expression.Lambda( expr, paramExpr);
}
public Expression CompleteExpression
{
    get
    {
        var paramExp = Expression.Parameter(Type, "left");
        if (this.Count == 0) return Expression.Lambda
        (Expression.Constant(true), paramExp);
        LambdaExpression lambda1 = this.First().MakeExpression(paramExp);
        var ret = lambda1.Body;
        foreach (var c in this.Skip(1))
            ret = Expression.MakeBinary((ExpressionType)c.CombineOperator, 
        ret, c.MakeExpression(paramExp).Body);
        return Expression.Lambda(ret, paramExp);
    }
} 

The CompleteExpression creates an input parameter out of the ObjectType and a lambda out of the first ExpressionVM. Then it loops through the rest of the ExpressionVMs appending the Expressions it gets from their MakeExpressions to the lambda based on their CombineOperator.

Other Interesting Code Snippets

I provide lookup lists for each ComboBox.

The AvailableProperties is populated every time the ObjectType changes.

C#
set { 
        _ObjectType = value;
        AvailableProperties = from p in value.GetProperties()
                    where GetSupportedTypes().Contains(p.PropertyType)
                    select p;
        OnPropertyChanged("ObjectType");
    }

The CombineOperator and the CompareOperator are both enums. So their lists are generated on the fly like this:

C#
public Array AvailableCompareOperators
{
    get { return Enum.GetValues(typeof(ComparisonOperators)); }
}

To Do

Yes, there is still a lot left to do, and there is a lot of room for improvement.
Here is a short list that I came up with:

  • Add more operators
  • Allow ANDs and ORs to be nested
  • Allow sub properties to be selected
  • Make WPF select specialized templates based off the PropertyType

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)