Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / WPF

Visual Expression Builder for Dynamic Linq

Rate me:
Please Sign up or sign in to vote.
4.73/5 (14 votes)
2 Feb 2012CPOL4 min read 110.4K   2.4K   96   29
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)


Written By
United States United States
Co-director of Chabad Student Center @ SUNY New Paltz
Lecturer of Computer Science @ SUNY New Paltz

Comments and Discussions

 
QuestionHow to input an expression? Pin
Member 110171727-Oct-14 9:30
Member 110171727-Oct-14 9:30 
QuestionGetting Started with the expression builder Pin
Member 946477722-Jul-14 4:47
Member 946477722-Jul-14 4:47 
Questionstring value could be null Pin
SLX6-Mar-14 10:25
SLX6-Mar-14 10:25 
QuestionDataTypes Pin
CodePug1-Aug-13 8:16
CodePug1-Aug-13 8:16 
AnswerRe: DataTypes Pin
Rabb Moshe Plotkin1-Aug-13 9:35
Rabb Moshe Plotkin1-Aug-13 9:35 
GeneralRe: DataTypes Pin
CodePug2-Aug-13 6:18
CodePug2-Aug-13 6:18 
QuestionIt doesnt work what is my mistake ? Pin
sven197825-Nov-12 7:57
sven197825-Nov-12 7:57 
AnswerRe: It doesnt work what is my mistake ? Pin
sven197826-Nov-12 7:17
sven197826-Nov-12 7:17 
GeneralRe: It doesnt work what is my mistake ? Pin
Rabb Moshe Plotkin1-Aug-13 9:40
Rabb Moshe Plotkin1-Aug-13 9:40 
QuestionI added 'StartsWith' and 'EndsWith' to MakeExpression Pin
Paul Brower23-Mar-12 9:27
Paul Brower23-Mar-12 9:27 
QuestionWinForms version? Pin
Paul Brower21-Mar-12 4:27
Paul Brower21-Mar-12 4:27 
AnswerRe: WinForms version? Pin
Paul Brower22-Mar-12 2:29
Paul Brower22-Mar-12 2:29 
QuestionRe: WinForms version? Pin
bsculley26-Mar-12 12:34
bsculley26-Mar-12 12:34 
AnswerRe: WinForms version? Pin
Paul Brower26-Mar-12 15:33
Paul Brower26-Mar-12 15:33 
GeneralRe: WinForms version? Pin
wvd_vegt27-May-13 23:53
professionalwvd_vegt27-May-13 23:53 
GeneralRe: WinForms version? Pin
goud12329-May-14 20:53
goud12329-May-14 20:53 
QuestionProject VS Pin
mdenicola1-Mar-12 6:42
mdenicola1-Mar-12 6:42 
AnswerRe: Project VS Pin
ksecrist19-Jul-12 10:06
ksecrist19-Jul-12 10:06 
GeneralMy vote of 5 Pin
Dean Oliver5-Feb-12 23:11
Dean Oliver5-Feb-12 23:11 
GeneralMy vote of 4 Pin
HoyaSaxa933-Feb-12 6:37
HoyaSaxa933-Feb-12 6:37 
QuestionSample Project Pin
Shaun Brown16-Aug-11 6:45
Shaun Brown16-Aug-11 6:45 
GeneralContains/Like Pin
Neil Alderson21-Jul-10 0:11
Neil Alderson21-Jul-10 0:11 
GeneralRe: Contains/Like Pin
Rabb Moshe Plotkin21-Jul-10 2:28
Rabb Moshe Plotkin21-Jul-10 2:28 
GeneralRe: Contains/Like Pin
Neil Alderson6-Aug-10 1:33
Neil Alderson6-Aug-10 1:33 
GeneralRe: Contains/Like Pin
Rabb Moshe Plotkin6-Aug-10 2:20
Rabb Moshe Plotkin6-Aug-10 2:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.