Domanda

so I want to make filtering work automaticly based on some easy settings. The code I have is this:

public ActionResult Index() // here I want to add filtering for Status I only want to show the active ones
{
    IQueryable<Ticket> cases = db.Cases().AsQueryable();
    cases = cases.EnablePaging().EnableFilterFor(x => x.Status);
    return View(cases);
}

EnableFilterFor looks like this:

public static IQueryable<T> EnableFilterFor<T>(this IQueryable<T> queryable, Expression<Func<T, string>> keySelector)
{ 
    string filterValue= "Active";
    //Expression<Func<T, bool>> whereexpresion = keySelector.Compile() == "Active"

    queryable = queryable.Where(
       //here do the magic !! so that the result will be 'x=>x.Status == filterValue');
    );
    return queryable;
}

I googled a lot, tried many different things but no success. I somehow have to combine the keySelector and the filterValue to work (I need an Expression for the Where method to work). Any help would be greatly appreciated.

EDIT: After testing both solutions (thank you both!) I found out that Poke had the best one. Poke his code is the only code that doesn't change the way that the SQL is generated. When I took a look at Servy his generated SQL it always did an EXTRA Sql select query and an EXTRA and in the WHERE clause... No idea why :)

È stato utile?

Soluzione

IQueryable.Where requires an Expression<Func<T, bool>>, so that will be the thing we need to build. As we want to integrate something from another expression (a Expression<Func<T, string>>), we have to build the expression “by hand”.

So in the end, we want to call LambdaExpression.Lambda<Func<T, bool>>(…) to get our expression for Where, but we need to fill in the expression body:

// first, we reuse the parameter from the `keySelector` expression
ParameterExpression param = keySelector.Parameters[0];

// The body is now just an equality comparison of the `keySelector`
// body, and the constant `filterValue`
Expression body = Expression.Equal(keySelector.Body, Expression.Constant(filterValue));

// now we just need to create a lambda expression for that body with the
// saved parameter and it’s all done:
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(body, param));

Altri suggerimenti

What we'll need here is a Compose method, for expressions. It'll take an expression that uses a value, and another expression that conceptually will use the result of the first expression as its input, generating a new output.

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

It will require the ability to replace one expression with another, which we can do using the following:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

Now we can write:

public static IQueryable<T> EnableFilterFor<T>(
    this IQueryable<T> queryable, 
    Expression<Func<T, string>> keySelector)
{ 
    string filterValue= "Active";

    return queryable.Where(keySelector.Compose(status => status == filterValue));
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top