Josh Heyse

Thoughts Defragmented

A Richer DynamicFilterRepeater: Part 2 – Advanced Parameters

Posted by admin on April 5th, 2008

After extending the LinqDataSource in Part 1, I was curious to see what other types of parameters I could make.  My first idea was to create composite parameters for and/or operations.  The existing implementation of the LinqDataSource only allows you to append filter criteria to the list, which creates an AndAlso functionality.

Continuing with the AdventureWorks Products example, I devised a scenario where composite parameters would be useful. The Products in the AdventureWorks database allow you to store the weight of the item in either lbs or grams. This requires the use of a composite parameter to select all products which fall in between 2 – 3 lbs or the equivalent 907 – 1360 grams.  Since I have been playing with WPF and Silverlight I have taken a liking to the declarative syntax. The following query looked like this in my head:

<cc1:DynamicLinqDataSource ID="GridDataSource" runat="server">
    <WhereParameters>
        <cc1:OrExpressionParameter>
            <Parameters>
                <cc1:AndExpressionParameter>
                    <Parameters>
                        <cc1:EqualsExpressionParameter Name="WeightUnitMeasureCode" Value="LB" />
                        <cc1:RangeExpressionParameter Name="Weight" MinValue="2" MaxValue="3" Type="Int32" />
                    </Parameters>
                </cc1:AndExpressionParameter>
                <cc1:AndExpressionParameter>
                    <Parameters>
                        <cc1:EqualsExpressionParameter Name="WeightUnitMeasureCode" Value="G" />
                        <cc1:RangeExpressionParameter Name="Weight" MinValue="907" MaxValue="1360" Type="Int32" />
                    </Parameters>
                </cc1:AndExpressionParameter>
            </Parameters>
        </cc1:OrExpressionParameter>
    </WhereParameters>
</cc1:DynamicLinqDataSource>

Once I had the use case for the feature completed it was time to refactor my code to support the new functionality.  As I am writing this I am kicking myself for not creating some corresponding UnitTests for this little project.  If I have some time I will write up some tests before I release the code base to the public.

Anyway… To support the change in functionality I had to make several changes:

  • Modify IDyanmicExpressionParameter
  • Extend ParameterCollection
  • Create CompositeExpressionParameterBase

First, I had to modify the IDynamicExpressionParameter interface to support an additional method called GetLambdaExpression.  The reason for this is that we can’t just append to the query anymore, the composite parameters must compile their own binary tree of Lambda Expressions to work correctly.

public interface IDynamicExpressionParameter
{
    IQueryable AppendQuery(IQueryable query);

    LambdaExpression GetLambdaExpression(Type itType);
}

Next, I needed to extend the ParameterCollection to accept parameters beyond the built in ones with the .NET Framework.  Under my previous implementation parameters were not being tracked in ViewState and this was causing an issue with postbacks.  The DynamicParameterCollection accepts additional Parameter types, but still uses a hard coded list.  A better implementation would be to create a static list of knownTypes that appends new knownTypes as they are found.

public class DynamicParameterCollection : ParameterCollection
{
    private readonly Type[] _knownTypes = new Type[] { typeof(AndExpressionParameter),
                                                        typeof(OrExpressionParameter),
                                                        typeof(InExpressionParameter),
                                                        typeof(LikeExpressionParameter),
                                                        typeof(RangeExpressionParameter),
                                                        typeof(EqualsExpressionParameter) };

    protected override object CreateKnownType(int index)
    {
        int baseTypesLength = base.GetKnownTypes().Length;
        if(index < baseTypesLength)
            return base.CreateKnownType(index);

        return Activator.CreateInstance(_knownTypes[index - baseTypesLength]);
    }

    protected override Type[] GetKnownTypes()
    {
        List<Type> knownTypes = new List<Type>(base.GetKnownTypes());
        knownTypes.AddRange(_knownTypes);
        return knownTypes.ToArray();
    }
}

Lastly, I implemented CompositeExpressionParameterBase which supports both And and Or binary tree operations.  There were two key pieces to this class.

The first was wiring up the IStateManager interface to work properly with the parent DynamicParamterCollection.  This proved fairly difficult to debug before I remembered that I could now step into the .NET Framework using Visual Studio 2008. This was my first experience doing so, and I think it is a HUGE feature. The VS team rocks!

The next step was to implement binary trees as Lambda Expressions.  This forced me to get a little deeper into Lamdba Expressions and the ExpressionTreeVisualizer example paid off big time.  You can find the Debug Visualizer in the %PROGRAM FILES%Microsoft Visual Studio 9.0Samples1033CSharpSamples.zip directory.

LinqExpressionVisualizer

The code for creating a binary tree expression looks likes this:

protected LambdaExpression GetLambdaExpression(Type itType, ExpressionType binaryType)
{
    List<LambdaExpression> lambdas = new List<LambdaExpression>();
    foreach (Parameter p in Parameters)
    {
        LambdaExpression lambda = p.GetLambdaExpression(itType);
        if (lambda != null)
            lambdas.Add(lambda);
    }

    if (lambdas.Count == 0)
        return null;

    if (lambdas.Count == 1)
        return lambdas[0];

    LambdaExpression left = lambdas[0];
    LambdaExpression right = lambdas[1];

    ParameterExpression param = Expression.Parameter(left.Parameters[0].Type, "");
    BinaryExpression root = Expression.MakeBinary(binaryType, left.Body, right.Body);
    for (int i = 2; i < lambdas.Count; i++)
    {
        right = lambdas[i];
        root = Expression.MakeBinary(binaryType, root, right.Body);
    }

    left = Expression.Lambda(ReplaceParameter(root, param), param);
    return left;
}

Some other ideas for advanced parameters include a NotNullOrEmpty parameter and a Not parameter which inverses the result of the composite expression.  Up next I will talk about a writing web controls to expose this advance functionality to users.

Download Solution DynamicData.zip

One Response to “A Richer DynamicFilterRepeater: Part 2 – Advanced Parameters”

  1. A Richer DynamicFilterRepeater - Part 4 Custom DynamicFilterControls & Expressions « Josh Heyse Says:

    [...] post 2 in this series I contrived a scenario where composite parameters were used to select products from [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>