0

I'm trying to get my head around combining expressions, looking around it should be possible to do what I'm trying to do but I'm struggling.

I have an Expression<Func<class, dynamic>> which defines my selection that I eventually run against my EF context, where class is a table I'm selecting data from. For example:

Expression<Func<foo, dynamic>> expression = foo => new
{
   RecordExists = foo.bar.Exists,
   RecordHasPaid = foo.bar.Amount != null && foo.bar.Amount > 0
}

// run the expression against the database and get the results...
var results = context.foo.Select(expression);

What I want to be able to do is to then extract common logic within that expression out into static "helper" style expressions I can re-use, for example, rather than having this line within my first expression

RecordHasPaid = foo.bar.Amount != null && foo.bar.Amount > 0

I could have a re-usable expression, stored in a re-usable class, say ExpressionHelper, that I can then insert into my first expression:

public static Expression<Func<bar, bool>> BarHasPaid(bar foobar)
{
   return b => b.Amount != null && b.Amount > 0;
}

Becomes:

Expression<Func<foo, dynamic>> expression = foo => new
{
   RecordExists = foo.bar.Exists,
   RecordHasPaid = ExpressionHelper.BarHasPaid(foo.bar)
}

// run the expression against the database and get the results...
var results = context.foo.Select(expression);

Note that I am passing in a different parameter to this expression than my outer expression (hopefully I can be that generic).

Sounds great, compiles fine, however - when I come to run the .Select I get the dreaded error:

LINQ to Entities does not recognize the method 'System.Linq.Expressions.Expression1[System.Func2[bar,System.Boolean]] BarHasPaid(bar)' method, and this method cannot be translated into a store expression.

I understand the reason why this happens (at least I think I do) in that my method is not known so cannot be translated into SQL, and additionally my expression cannot be turned into an Expression Tree which makes sense to EF because I am essentially using two different expressions within each other and it doesn't know how to turn them into something that makes sense.

I've tried various things, combining the expressions such as here which unfortunately seems to work when you have two expressions, not one expression inside another expression.

I've also tried implementing the wrap/unwrap with custom ExpressionVisitors as mentioned here (I did eventually get this to compile fine, but I get the same error)

I've also tried many other similar answers and tried using LinqKit to Expand my expression and also set my context with AsExpandable to no avail. All of my attempts so far have resulted in either the above error, or this one when trying to combine using LambdaExpression.Lambda to compile my combined expression:

variable 'foo' of type referenced from scope '' but it is not defined

Which I believe is being thrown because the variable foo which is used within my outer expression, Expression<Func<foo, dynamic>>, does not exist within my expression Expression<Func<bar, bool>> because the resulting expression when they are combined would make no sense.

Unfortunately I don't think I have a deep enough understanding of exactly what it is I'm trying to do, so I've hit a bit of a dead end as to where to turn next. Any suggestions as to if this is possible and/or how to achieve it would be greatly appreciated.

EDIT: I have created two sample projects using Rextester, this is the working example and this is what causes the error - using this online tool you can actually see what EF should be running but as there is no EF to run it against the error doesn't actually occur during that test.

Danny Lager
  • 371
  • 1
  • 4
  • 17
  • @DannyLager, use the expression vistiors, and replace the parameter in the inner, lambda. e.g when you write `x => x.something` and you combine it with `x => x.somethingElse` Just because the parameters are named the same doesn't make them the same you need to rewrite the expression to use the same parameter – johnny 5 Aug 03 '18 at 18:12
  • Your definition for `BarHasPaid` seems problematic - it has a parameter `foobar` that is never used? – NetMage Aug 03 '18 at 19:41
  • When attempting to implement this, I ran into a bigger issue - when the compiler compiles `Expression`, it uses the type of `BarHasPaid` as the type of the `RecordHasPaid` anonymous member, which is `Expression<>` not `boolean`. At this point you are starting to re-write so much of the `Expression` you are buliding a compiler. – NetMage Aug 03 '18 at 20:15
  • @NetMage the func within the expression can't contain the same name as the parameter passed in, it's somehow clever enough to know that foobar is the parameter of the func and you can give it another alias, it is quite annoying... regarding the type of BarHasPaid as the type of RecordHasPaid, I'll do some digging on that – Danny Lager Aug 06 '18 at 08:09

1 Answers1

-1

Your code

Expression<Func<foo, dynamic>> expression = foo => new
{ RecordExists = foo.bar.Exists,
  RecordHasPaid = ExpressionHelper.BarHasPaid(foo.bar)
}

does not return the desired result. The type of RecordHasPaid is Expression<Func<bar, bool>> instead of just bool. It is obvious that EF gets confused by this.

Basically you want to invoke another expression within the body of your expression rather than returning a lambda expression. Unfortunately AFAIK this is impossible with LINQ syntax.

But if you deal with expression trees, you can do the job. Have a look here: Combining two lambda expressions in c#

Dealing with Expression trees lacks beauty and type safety, but it is a powerful sword. It's a pity that the compiler refuses to support the full set of features of expression trees. Lambda invocations are missing as well as loops.

Marcel
  • 1,688
  • 1
  • 14
  • 25
  • There is no (literal) lambda invocation syntax, so I am not sure that is missing. Loops are not expressions, but worse than that, Expression Trees doesn't support all modern C# expression constructs either! (e.g. null conditional access). – NetMage Aug 06 '18 at 17:16
  • @NetMage I am pretty sure that the null-coalescing operator *is* supported by Expression Trees. Simply because it is just syntactical sugar that can also be expressed by the conditional operator. – Marcel Aug 07 '18 at 21:11
  • Yes, but not the [null-conditional operators](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators) which were added in C# 6.0 without `Expression` tree support. – NetMage Aug 10 '18 at 04:01