Skip to main content

Multiple complex clauses joined with OR operation in where function

How is it possible to filter data based on some set of filters or expressions that should be used with or operation in where clause?
For example, there is a class:

class DTOFilter
{
    public string Domain { get; set; }
    public string Mobile { get; set; }
}

It is required to filter Users list based on the list of the filters next way:

u=>
(u.Email.Contains(filters[0].Domain) 
   && u.PhoneNumber.StartsWith(filters[0].Mobile)) ||
(u.Email.Contains(filters[1].Domain) 
   && u.PhoneNumber.StartsWith(filters[1].Mobile)) || 
...
But the more usable form would be:
db.Users.Where(filters.Filter<Users, DTOFilter>(
                        (u, f) => u.Email.Contains(f.Domain) 
                               && u.PhoneNumber.StartsWith(f.Mobile))
              .Or())
To perform that task it's required to implement two functions:
  1. Or expression join function
  2. Filter data split function
Or function goes through a collection of expressions and joins them in a Or expression like (((expression1 or expression2) or expression3) or expression4)

public static Expression<Func<T, bool>> Or<T>(
    this IEnumerable<Expression<Func<T, bool>>> source)
    {
        var expressions = source.ToList();
        if (expressions.Count == 0)
            return x => false;

        var orExpression = expressions
            .Select(e => e.Body)
            .Aggregate((a, b) => Expression.OrElse(a, b));
        var parameter = expressions.First().Parameters.First();
        return Expression.Lambda<Func<T, bool>>(orExpression, parameter);
    }
This function can already be used if filters are already valid expressions.
Filter split data function, should multiply the selector expression and replace in the result expression parameter with the exact object value. Filter data population is done in the function:
public static IEnumerable<Expression<Func<T, bool>>> Filter<T, TData>(
    this IEnumerable<TData> data,
    Expression<Func<T,TData, bool>> selector)
{
    var parameter = selector.Parameters.First(
                                p => p.Type.IsAssignableFrom(typeof(T)));

    return data.Select(item => Expression.Lambda<Func<T, bool>>(
        new DataVisitor<TData>(item).Visit(selector.Body), parameter));
}

To replace parameter with the exact value was implemented simple visitor:
public class DataVisitor<T> : ExpressionVisitor
{
    private readonly ConstantExpression _data;

    public DataVisitor(T dataItem)
    {
        _data = Expression.Constant(dataItem);
    }

    protected override Expression VisitParameter(
                                        ParameterExpression node)
    {
            return node.Type.IsAssignableFrom(typeof(T))
                    ? _data : base.VisitParameter(node);
    }
}
The build expression is a correct lambda expression that can be parsed by the EF expression tree parser. (Check community comments)

Comments

Popular posts from this blog

[Obsolete] Azure package unpack and repack

Azure package There are a lot of possible scenarios of Azure deployment. Most of them are based on package that we can define and create with Visual Studio. If you need to change package use Visual studio to repack your project. To prepare specific web . config file, use different build configuration with transformation (web . config . release, web.config.debug etc.) Only when it's not possible to use Visual Studio to repack Azure package, then it's possible to repack it the way described below.

MS Dynamics CRM solutions storage model

It's not a secret that there are two types of solutions exists in CRM: managed and unmanaged. Each has it's own properties and capabilities. In general solutions can be differentiated by next points Managed solution Unmanaged solution Completed solution Cannot be exported Deleted with all its changes and components Under development Can be exported as managed Can’t undo changes done by import   Let's see what makes them behaves in a different way.