0

Background

I have a method that returns a Linq expression with the following signature:

public static Expression<Func<string, bool>> GetStringFilterExpression(IEnumerable<string> fields)

Which I then place into a local variable:

var filterby = GetStringFilterExpression(new[] {"Copy*Z"});

Given a Linq2Sql data context db and a table DbItems, this allows me to do this:

var names = db.DbItems.Select(d=>d.Name).Where(filterby);
var result = db.DbItems.Where(d=>names.Contains(d.Name);

Or I can use this helper method

public static IQueryable<T> MatchesFilter<T>(IQueryable<T> query, Expression<Func<T, string>> selector, Expression<Func<string, bool>> filter)
{
    LambdaExpression lambda = (LambdaExpression)selector;
    var predicate = Expression.Invoke(filter, selector.Body);
    var lambdaPredicate = Expression.Lambda<Func<T, bool>>(predicate, lambda.Parameters);
    return query.Where(lambdaPredicate);
}

And perform a slightly more concise query:

var result = MatchesPattern<DbItems>(db.DbItems, d=>d.Name, filterby);

Both methods work, returning the same results, and running the expression as a correctly filtered SQL query (no local collection).

Goal

What I would like to do is use the function inside the normal .Where() clause, so it can be combined with other logic or invoked on more than one field, for example:

var result = db.DbItems.Where(d => filterby(d.Name) && d.Active);

But to do that, I have to use a Func rather than an Expression. I tried unwrapping the expression like so:

public static Func<string, bool> CreateStringFilter(Expression<Func<string, bool>> filterby)
{
    return p => filterby.Invoke(p);
}

var inlinefilter = CreateStringFilter(filterby);

Which does allow this to work:

var result = db.DbItems.ToList().Where(d => inlinefilter(d.Name));

However, that requires pulling the whole table down into a local collection. If I remove .ToList(), I get a "no supported translation to SQL" error.

Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.

Even though it's the exact same code that runs as SQL when it's wrapped in the Expression.

Is there any way to get this to work as an inline string function in the .Where (or .Count, etc.) clause against the live data context instead of a local collection?

Follow-up/Workaround

Marked as duplicate, but I didn't feel the linked question answered this question, which is how to use an existing string,bool Expression against an arbitrary property (vs. creating custom property-aware expression trees in the accepted answer to the other question). Here's the workaround I came up with:

Add another helper method like this:

public static Expression<Func<T,bool>> PropFilter<T,T2>(Expression<Func<T, T2>> selector, Expression<Func<T2, bool>> filter)
{
    LambdaExpression lambda = (LambdaExpression)selector;
    var predicate = Expression.Invoke(filter, selector.Body);
    return Expression.Lambda<Func<T, bool>>(predicate, lambda.Parameters);
}

Then I can create property-specific filters based on the original string filter, and chain them like this:

var filtername = PropFilter<DbItems, string>(d => d.Name, filterby);
var filterdesc = PropFilter<DbItems, string>(d => d.Description, filterby);
var propfilter = filtername.Or(filterdesc);

Finally, I am able to invoke that filter in Linq and combine with inline property filters like so:

var result = db.DbItems.Where(propfilter.And(d => d.Active));

I gather that's as close as I'm going to get to the original desired syntax.

Rich
  • 149
  • 1
  • 9
  • I added follow-up and workaround after this was marked as duplicate. I didn't feel the linked question answered this one specifically in how to use an existing string expression in a property-blind manner. If duplicate status is removed I will add my workaround as an answer. Thanks. – Rich Oct 09 '17 at 15:37
  • @servy I do not believe this is a duplicate of [this question](https://stackoverflow.com/questions/457316/combining-two-expressions-expressionfunct-bool) as that is specifically about combining expressions. This question is about applying an existing expression of a different type against arbitrary properties of a parent type as shown in the answer I have supplied. – Rich Oct 09 '17 at 17:46
  • @servy While [this question](https://stackoverflow.com/questions/37602729/convert-linq-expression-obj-obj-prop-into-parent-parent-obj-prop) is useful background, it does not answer the current question specific to how to utilize existing base type expressions as directly as possible in the .Where clause in Linq. So I think this question and answer still add value and searchability. – Rich Oct 09 '17 at 18:08
  • It solves the exact problem you've said that you have. In what way do you think it fails to answer the question you asked? – Servy Oct 09 '17 at 18:14
  • I agree that it's conceptually related, and the _answer_ to that other question is very useful for coming up with an answer to this one. But it's not the same question. I didn't find that Q+A prior to posting this, but even if I had I don't think I would have recognized it as the same problem, perhaps because it lacks any specific example of usage. This question is specifically about how to use base class expressions against parent properties in the .Where() clause, and the other question is only abstractly related to that. – Rich Oct 09 '17 at 20:52
  • The fact that you didn't realize that your question is *the exact same question* doesn't make the post not a duplicate. It still answers your question. It's trying to solve the *exact same* problem you're trying to solve. That the reason you want to solve that problem is different than the reason someone else wants to solve that problem doesn't change what the problem, or the solution, is. Why you want to compose one expression with another doesn't impact how you go about composing one expression with another. – Servy Oct 09 '17 at 20:56

1 Answers1

1

As noted in the workaround added to the question, it is not possible to achieve the exact syntax requested. However, something close can be achieved as follows:

Add a helper method to apply an expression of type T2,bool to a T2 property of the original type T:

public static Expression<Func<T,bool>> PropFilter<T,T2>(Expression<Func<T, T2>> selector, Expression<Func<T2, bool>> filter)
{
    LambdaExpression lambda = (LambdaExpression)selector;
    var predicate = Expression.Invoke(filter, selector.Body);
    return Expression.Lambda<Func<T, bool>>(predicate, lambda.Parameters);
}

Then use that to create property-specific filters based on the original string filter and any properties, and then optionally chain them like this:

var filtername = PropFilter<DbItems, string>(d => d.Name, filterby);
var filterdesc = PropFilter<DbItems, string>(d => d.Description, filterby);
var propfilter = filtername.Or(filterdesc);

Finally, combine with inline property filters using Expression chaining syntax like so:

var result = db.DbItems.Where(propfilter.And(d => d.Active));
Rich
  • 149
  • 1
  • 9