How to convert Expression<Func<T, bool>> to Expression<Func<Type, bool>>?

expression-trees functional-programming generics linq reflection

Question

I am trying build a dictionary of Expressions that have different input parameter types. I am trying to store the type of the parameter because later down the road I plan to use Reflection to discover a method on the type. Here is the code that creates the dictionary and a generic Add function I created to add entries to it:

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public IDictionary<Type, Expression<Func<Type, bool>>> Entities { get; set; } 
    public LoadEntityQuery()
    {
        Entities = new Dictionary<Type, Expression<Func<Type, bool>>>();
    }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Expression<Func<Type, bool>> _lambda = null;

        if (where != null)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), where.Parameters[0].Name);

            var body = Expression.Invoke(where, param);
            _lambda = Expression.Lambda<Func<Type, bool>>(body, param);
        }

        Entities.Add(typeof(T), _lambda);
    }
}

The body of the new method is created properly. The issue is when I try to create the new Lambda expression with the type from the expression being passed in, I receive this error:

ParameterExpression of type 'TestNamespace.TestClass' cannot be used for delegate parameter of type 'System.Type'

Does anybody have an idea as to what I can do in this situation? Like I said before, at some point later I am going to loop through this dictionary to do some reflective programming on each entry. If there is a better way to do this I am all ears.

As an example of what I am trying to do, I store the expressions for Where clauses for POCO objects that need to be initialized:

LoadEntityQuery _query = new LoadEntityQuery();
    _query.Add<PayrollLocation>();
    _query.Add<PayrollGroupBU>();
    _query.Add<PersonnelPosition>(t => t.DataSet == MasterDataSet);
    _query.Add<EmployeeStatus>();
    _query.Add<PayrollGrade>();

This list of Entities will be different for each app. The idea is to collect all the entities and Where clause for each and discover a certain method using reflection on each one. (e.g. PayrollLocation has a GetPayrollLocationsQuery() method, PayrollGroupBU has a GetPayrollGroupBUQuery() method...). The Add method is generic in order for me to make use of the lambda expression in the calling code.

Thanks, Jason

Accepted Answer

Looking closely at your code, the expression you generate has some problems. See my explanation at the top of this answer to explain one of them, it's the same issue here. You're creating a new lambda where the parameter instance you create here is not used in the body.

The bigger problem is that your expressions are just wrong for what you appear to be trying to do. As far as I can tell, you are just trying to create a mapping from entity types to functions that take an entity of that type and returns a bool. Type -> Expression<Func<TEntity, bool>>. The expression you build just does not work.

You should make the dictionary store non-generic lambdas that way you can store these functions easily without performing conversions or rebuilding the expressions. You will not be able to store them as generic lambdas here. Then cast to the generic lambda when you access them. I'd put this in a separate class to manage the casting and refactor your code to this:

// add all necessary error checking where needed and methods
public class EntityPredicateDictionary
{
    private Dictionary<Type, LambdaExpression> dict = new Dictionary<Type, LambdaExpression>();

    public Expression<Func<TEntity, bool>> Predicate<TEntity>() where TEntity : Entity
    {
        return (Expression<Func<TEntity, bool>>)dict[typeof(TEntity)];
    }

    public LambdaExpression Predicate(Type entityType)
    {
        return dict[entityType];
    }

    internal void Add<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : Entity
    {
        dict.Add(typeof(TEntity), predicate);
    }
}

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public EntityPredicateDictionary Entities { get; private set; }
    public LoadEntityQuery()
    {
        Entities = new EntityPredicateDictionary();
    }

    public void Add<TEntity>(Expression<Func<TEntity, bool>> predicate = null) where TEntity : Entity
    {
        Entities.Add(predicate);
    }
}

// then to access the predicates
LoadEntityQuery query = ...;
var pred1 = query.Entities.Predicate<Entity1>();
var pred2 = query.Entities.Predicate(typeof(Entity2));

Popular Answer

I don't think this is going to do what you expect it to do; Func<Type, bool> defines a function that takes as a parameter a type and returns a bool. Func<T, bool> defines a function that takes as a parameter an object of type T and returns a bool. The lambda that is defined in your dictionary is never going to receive the object that you are trying to filter on, only its type.

To me the quickest way to make this in any way appropriate would be to make the LoadEntityQuery class generic on the type of the parameter that you expect your function to accept, but that probably will limit you in other ways...

You could use an object and cast it... Not the best solution, but at least it encapsulates the casting and is a fairly small piece, while still allowing you to do what it appears to me you need to do.

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    // Note: Hide this, we don't want anyone else to have to think about the cast.
    private IDictionary<Type, object> Entities { get; set; }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Entities.Add(typeof(T), where);
    }

    public Expression<Func<T, bool>> Retrieve<T>() where T : Entity
    {
        if (!Entities.ContainsKey(typeof(T)))
            return null;
        return (Expression<Func<T, bool>>)Entities[typeof(T)];
    }
}



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why