Skip to content

Instantly share code, notes, and snippets.

@DrSammyD
Created April 8, 2014 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DrSammyD/10165615 to your computer and use it in GitHub Desktop.
Save DrSammyD/10165615 to your computer and use it in GitHub Desktop.

FilterExpressionComposer

This is a way of filtering an IQueryable using Dynamic Linq. It composes strings based on Strings retrieved from the database and static values and functions which return strings stored in a dictionary with a key of a columnName for the filter Criterion

Each filter criterion has on it an appColumn which contains the lookup name for the path of the properties on a queryable which it needs to filter on.

FuncVal

public class FuncVal<TC>
{
    public Func<TC, string> Func;
    public String Val;
}

This is the structure of the class inside of the valueDictionary.

It can either return a static string, or it can return a function which takes a controller to be used for finding variables like user name, session variables or anything that needs to be calculated. It too must return a string and can be crated as a lambda

FilterExpression

This function is a static function on FilterExpressionComposer. It takes 5 arguments and returns a single filter expression string usable by Dynamic linq.

Let's take a real world example from inside a PatientController

public class PatientController : Controller{
    public ActionResult Index(){
        var colName = "Acuities";
        var express = ".Any(,#{=|Serious,Critical}";
        var pathDict = new Dictionary<String, String>
        { 
            {"Acuities", "Patient.Reports[]Condition.Trim()"}
        };
        var valueDict = new Dictionary<String, FuncVal<PatientsController>>
        {
            {"Serious", new FuncVal<PatientsController>(){Val = "\"Serious\""}},
            {"Critical", new FuncVal<PatientsController>(){Val = "\"Critical\""}}
        };
        FilterExpressionComposer.FilterExpressions(colName,express,this,pathDict,valueDict);
    }
}

Let's take a look at the Acuities value in pathDict Patient.Reports[]Condition.Trim()

This is a path to a condition property inside of some object. That object has a property patient. Patient has an array of Reports, and each Report has a property Condition. We need to tell the function that Reports is an Array, so that it knows where to put the operators inside of express. The operands are held inside of .Any(,#{=|Serious,Critical} Which first parses out any value containers #{=|Serious,Critical} and calls .Split(','). This creates two arrays of values and operators.

The function will call .Split("[]") and place the operators in order of appearence in the operators array. => Patient.Reports.Any(

Then it will use the next string from the .Split("[]") array => Patient.Reports.Any(Condition.Trim()

The function see's that there are no more operands so it appends the next String in the value array and closes any unclosed parenthesis. => Patient.Reports.Any(Condition.Trim()#{=|Serious,Critical})

The function will parse out exactly how to compare each of the values. We get the comparision operator from the characters before |, which in this case is =. It then calls .Split(',') which will give us two keys Serious and Critical to lookup values on. So it then finds the string values for those keys in the expressions dictionary. => \"Serious\" and => \"Critical\"

It then looks at where the #{=|Serious,Critical} value is in the string using the, finds the previous operand, which in this case is .Any( and copies the string inbetween the end of the operand and the end of the value with two comparisons => Condition.Trim()#{=|Serious,Critical}

It then copies that string for each value found and replaces #{=|Serious,Critical} with the found value from the coresponding FuncVal. => Condition.Trim()="Serious" And => Condition.Trim()="Critical"

It joins those on an Or and replaces Condition.Trim()#{=|Serious,Critical} with Condition.Trim()="Serious" Or Condition.Trim()="Critical" resulting in => Patient.Reports.Any(Condition.Trim()="Serious" Or Condition.Trim()="Critical")

We can now use this string on a queryable using dynamic linq

var filterExpression = FilterExpressionComposer.FilterExpressions(colName,express,this,pathDict,valueDict);
someQueryable.Where(filterExpression);

Note: the Trim() call is considered a modification property of the Condition property, so it's not an operand

Multiple inner filters

FilterExpressionComposer is also able to compare more than one property to different values inside of some array.

First construct our values and dictionaries.

public class PatientController : Controller{
    public ActionResult Index(){
        var colName = "PatientSelfAndType";
        var express = ".Any(,#{=|CurrentUserPaceartID}, And ,#{=|AttendingType}";
        var pathDict = new Dictionary<String, String>
        { 
            {"PatientSelfAndType", "Patient.PhysicianPatients[]Physician.PhysicianUser.User.Username[]PhysicianPatientType.Type"}
        };
        var valueDict = new Dictionary<String, FuncVal<PatientsController>>
        {
            {"CurrentUserPaceartID", new FuncVal<PatientsController>(){Func = ((controller)=> GetCurrentUserPaceartID(controller)) }},
            {"AttendingType", new FuncVal<PatientsController>(){Val = "2"}}
        };
        FilterExpressionComposer.FilterExpressions(colName,express,this,pathDict,valueDict);
    }

    public static String GetCurrentUserPaceartID(PatientsController controller)
    {
        return "\"" + controller.User.Identity.Name + "\"";
    }
}

Again we split the path on [] and construct the start of our filter string with the first operand we find and go deeper into the object => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username

Then we come to an And operand in express. The way this works is if the operand doesn't start with . then the function will use the first value from the filter expression, and then place the current opperator => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username#{=|CurrentUserPaceartID} And Notice, the And has two buffer spaces around it. if the operand has no . then it's necessary to add this buffer otherwise the string will become part of the propertyname and likely throw a no property found on object exception.

Then it will use the next String from the .Split("[]") array => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username#{=|CurrentUserPaceartID} And PhysicianPatientType.Type

Because the And Didn't create a new context as the .Any( would did, we can see that PhysicianPatientType is still refering back to PhysicianPatients So we can filter based on that same object, makeing both conditions necessary for fulfilling the Any on PhysicianPatients. Now the function will use the next Value to compare. => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username#{=|CurrentUserPaceartID} And PhysicianPatientType.Type#{=|AttendingType}

The last operand has been used so it will close out any unclosed parenthesis => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username#{=|CurrentUserPaceartID} And PhysicianPatientType.Type#{=|AttendingType})

Note: We could have achieved the same effect by replacing the previous .Any( with a .Where( and the And with ).Any(. This would have output => Patient.PhysicianPatients.Where(Physician.PhysicianUser.User.Username#{=|CurrentUserPaceartID}).Any(PhysicianPatientType.Type#{=|AttendingType}) This however creates two separate Queries instead of combining them.

It then finds each value and replaces the value string with the value gotten from the valueDict. This one is slightly different. We see that the first FuncVal contains a lambda. This get's passed the context object (in this case the current PatientController) and returns some String, (in this case the current user's username). The third parameter and the gerneric Type passed into the FuncVal must match. The other Value is a simple int. => Patient.PhysicianPatients.Any(Physician.PhysicianUser.User.Username="SomeUserName" And PhysicianPatientType.Type=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment