1

I am trying to create an extension method that will be usable for both LINQ-to-Object and LINQ-to-Entities for creating a functioning Where query. More will go into it eventually but to start I am having an issue just getting the method to take a lambda column selection and use it as the base of a Contains() call. I have been able to make something work for LINQ-to-Objects, but when I try to use it for LINQ-to-Entities it has an issue.

Here is what works for LINQ-to-Objects:

public static IQueryable<T> WhereContains<T>(this IQueryable<T> query, Expression<Func<T, string>> column, IList<string> values)
{
    return query.Where(o => values.Contains(column.Compile().Invoke(o)));
}

If this is run against Entity Framework I get an exception stating

LINQ to Entities does not recognize the method...

I have a feeling this is going to require using an ExpressionVisitor, but I haven't been able to figure out how it needs to be wired in. Has anyone been able to accomplish this?

Update:

I would argue that this is not a duplicate being that the answer provided by xanatos showed a straight forward way of accomplishing this without using an ExpressionVisitor.

StuffOfInterest
  • 518
  • 3
  • 11
  • You're right that something like that is needed. LINQKit's `.AsExpandable()` will work here. This has been asked and answered before, let me see if I can find an answer that goes into detail. –  Jul 16 '18 at 13:14
  • I think `o.` shouldn't be there (`o => o.values...` should be `o => values...`) – Rafalon Jul 16 '18 at 13:21
  • You are right on the extra "o.". I was transcribing between systems. Have corrected the example above. – StuffOfInterest Jul 16 '18 at 13:59
  • @xanatos In what way is the question not a duplicate of the question it was closed as a duplicate of? – Servy Jul 16 '18 at 17:43
  • @Servy The othe question was about the composition of two expressions... This one is only the insertion of one in the other. The only common part seems to be the use of `Contains` as the method. If you look at the response of the other question, it has to use an `ExpressionVisitor`. – xanatos Jul 16 '18 at 17:45
  • @Servy The other post wanted to obtain `x.PossibleSubPath.MyStringProperty.Contains("some literal")`, this one wants to contain `x => someCollection.Contains(x.Property)` – xanatos Jul 16 '18 at 17:47
  • @xanatos This is literally composing two expressions. Not only are they both asking how to compose two expressions, they're even trying to compose them to do almost exactly the same thing. That there are a few trivial difference in property names and other entirely irrelevant details as to what the actual expressions are doing is entirely irrelevant. They're still both being composed. Every single question asking how two expressions are two be composed aren't "not duplicates" just because they have different property names in their expressions. That's absurd. – Servy Jul 16 '18 at 17:52
  • @Servy Other problem is that the accepted answer uses `Expression.Invoke`, that often doesn't play "nice" with ORMs (that are tagged in this question). – xanatos Jul 16 '18 at 17:54
  • @xanatos I see you didn't actually read the answer. Please read the answer before commenting on it further. – Servy Jul 16 '18 at 17:54
  • @Servy Yes, you used an `ExpressionVisitor` as the second choice... I hadn't noticed that the answer was *yours*. That answer is probably a more generic case of this question. There there are two variable expressions that need composing (and in fact I don't think you can do it as easily as here). Here there is only one variable expression and one fixed expression. – xanatos Jul 16 '18 at 17:56
  • @xanatos What the expressions are doing is irrelevant. You need to compose one with the other. The method does exactly that, for any two arbitrary expressions, unconditionally. All you need to do is compose them. That's it. You'd do it exactly like you would for the other two expressions for the other question, because the only difference is some minor property names that have *no effect* on the solution at all. – Servy Jul 16 '18 at 17:59
  • @Servy Your solution is a more complex solution to a more complex problem. My solution is an easier solution for a subset of the whole problem. 3 rows vs 15-20. – xanatos Jul 16 '18 at 18:10
  • @xanatos That you think you can provide a better solution doesn't make the questions not duplicates. Also "lines of code" is a very poor substitute for how "easy" a solution is. Outside of a re-usable library method that only ever needs to be looked at once (if at all), my solution creates a one-line entirely statically typed solution for every possible permutation of the problem. Making it much easier to write and to read, and removing numerous possibilities for errors. But again, if you think you can provide a better answer to the duplicate *that doesn't make the questions not duplicates*. – Servy Jul 16 '18 at 18:13

1 Answers1

1

Yes, you have to modify a little the Expression, but you don't need an ExpressionVisitor. It is much more simple.

public static IQueryable<TSource> WhereContains<TSource, TResult>(this IQueryable<TSource> query, Expression<Func<TSource, TResult>> column, IList<TResult> values)
{
    MethodInfo iListTResultContains = typeof(ICollection<>).MakeGenericType(typeof(TResult)).GetMethod("Contains", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(TResult) }, null);

    var contains = Expression.Call(Expression.Constant(values), iListTResultContains, column.Body);

    var lambda = Expression.Lambda<Func<TSource, bool>>(contains, column.Parameters);

    return query.Where(lambda);
}

Note that I've expanded it a little to cover more than string.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Perfect, thanks. I'm not able to use the generic result due to other constraints with what I'm doing with the data inside of the method but the example you provide nails the syntax. I need to dig into the expression building helpers a lot more. – StuffOfInterest Jul 16 '18 at 13:58