I'm trying to "share" a set of conditions between a Linq to Entities call and a some other code, to reduce possible mismatches in conditions between the two calls.
I started off by declaring my conditions:
private Func<DateTime, Status, bool> _submissionDateExpiredCondition =
(submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;
private Func<DateTime, Status, bool> _submissionDateWithinOneWeekCondition =
(submissionDate, status) => DateTime.Now < DbFunctions.AddDays(submissionDate, -7) && status == Status.Pending;
private Func<DateTime?, Status, bool> _bidValidityEndPeriodWithinThirtyDaysCondition =
(bidValidityEndPeriod, status) => bidValidityEndPeriod.HasValue && DateTime.Now < DbFunctions.AddDays(bidValidityEndPeriod.Value, -30) && (status == Status.OK);
I then want to use these conditions inside my where clause in both a Linq to Entities where call and as functions in an if statement (or possibly the where call of a Linq to Objects query):
myRepository
.FindAll()
.Where(x => x.Property == "value"
&& x.Data.AnotherProperty == true
&& _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)
|| _submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status)
|| _bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status))
and (please note that MyCustomObject is not the same type as returned by myRepository.FindAll()
)
private void ApplyConditions(List<MyCustomObject> items) {
foreach(var x in items){
if(_submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)){
x.Property = "condition 1";
}
else if(_submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status))
{
x.Property = "condition 2";
}
else if(_bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status))
{
x.Property = "condition 3";
}
}
}
But I keep bumping into the regular issues like
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
when executing the repository query...
I've tried building a predicate with a predicate builder (as per https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/) but no luck.
Can anyone point me in the right direction?
Late to the party, but someone may find my way of attacking the problem useful. However it can't easily be done without some expression manipulation.
Main problem is: inside the
.Where
's predicate expression you haveInvocationExpression
s of delegates (i.e. compiled code). EF has no way to find out what logic is baked into that delegates and thus won't be able to translate it into SQL. That's where the exception originates.The goal is to get a
.Where
predicate lambda expression that is logically equivalent to yours, yet understandable by EF. That means we have to get fromto
to be used in
, where
EntityType
is the element type of the queryable returned byFind
- the one that is different fromMyCustomObject
.Note that the invocation of the delegate is being replaced by it's defining expression (lambda body), with the (lambda) parameters
submissionDate
andstatus
replaced by the respective argument expressions of the invocation.If you define the conditions as delegates, their internal logic is lost in compiled code, so we have to start off with lambda expressions rather than delegates:
Using the lambda expression rather than the delegate, the compiler lets you rewrite the original predicate like this:
, which of course EF won't understand any better than before. What we however achieved is that the condition's internal logic is part of the expression tree. So all that's missing is some magic:
What
MAGIC
does: Find anInvocationExpression
of a delegate that is the result of aCompile()
method call on a lambda expression and replace it with the lambda's body, but make sure to replace the lambda parameters in the body with the argument expressions of the invocation.And here my implementation. Actually,
MAGIC
is calledExpress.Prepare
here, which is slightly less unspecific.