Is it possible to build a custom Expression<Func<T, bool>> by chaining "Or" operations to use in EF?
Hi all, this is my first post here.
I want to check if it's possbile to chain Expression.Or operations to be able to use it in EF 6 (Or in a
IEnumerable.Where()
in general.
Let me explain the context:
- I have two entities called Orders and Users. Users can create multiple orders, and one Order is related to one single User.
- Orders have a "CreatedDate" field (so that it is populated with DateTime.Now in the database) and also "CreatedBy" representing the username that created it.
- Users have a field called "DateDisabled" nullable. When it is null
, I can bring all his orders, but if it has a value, i will just bring orders dating from before he was disabled: order.DateCreated <= user.DateDisabled
.
To simplify:
a filter to bring orders from a single user would be :
since we can have many users, we would have many filter, one for each user:
var manyUsersOrders = _repository.Where(filter1 || filter2 || filter3 || ... || filterN);
Here comes the question: since it's not known how many users there are in the array, How can I create such an expression that chain Or
operations like that dinamically?
I tried to build Expressions and use Expression.Or
to join them together, but It returns a BinaryExpression
and I don't know exactly how to use it to build the final expression. Any help?15 Replies
you can chain multiple
.Where
calls and they will essentially be combined
dunno about or though dateDisabled
vary by filter or is it the same for every filter?
if it's the same, just check if the user ID is in a collection of user IDsit will vary for each user
I found a possible solution to this:
Gist
Extension method for combining predicates and extension method for ...
Extension method for combining predicates and extension method for or conditions in linq queries. - gist:4350639
In this gist, this person combines two expressions of same type by a
combination
which can be a BinaryExpression.Or
for example
Could be like expr1.Combine(BinaryExpression.OrElse, expr2)
;
Can be reduced to expr1.Or(expr2)
when chaning the implementation a bit
It Works!
So that's the solution:
ordersQueryForDebug
will render SQL Select statement correctly with the "where" clause just as expected. It also creates all the constants needed for the expressions created, like user.UserName
and dateDisabled
Instead of hydrating the users into memory, just join user on order in your where
Not 100% sure if that's your filter but that's the general idea of how you'd do it
Thanks for the help. That would be a viable solution too. I was avoiding to try and use Contains since Orders.CreateBy and User.UserName are not PK/FK correlations, thus it could impact performance somehow. Database in this project is all messed up and I can’t change it now 🥲
Hydrating into memory will be way worse, as you'll have to then do many individual queries instead of just 1 single query.
Not individual queries, but individual “where” clauses for each user. In the tests I made, it would create two variables for each user, their username and they dateDisabled and use them in the where clause.
A query filtering by 46 users is executed in roughly 1.2 seconds, which isn’t that bad if compared to the ideal scenario of having the PK/FKs and letting database management figure it out.
I will still go back to this and test what happens if I build relations between the tables in EF’s configuration and make the query normally, but multiple where clauses is acceptable since it was a production defect.
Is the "where" clause logically the same for each user though? It sounds like it should be
in which case you should be able to just write that as 1 single query that fetches all of the data in a single shot
largely speaking the only time you cant easily do that is when recursion is involved and you run into the turing problem, aside from that though you can most often get everything done in 1 big query which usually is many orders of magnitude more performant than many small queries
where clauses are different for each user
and again, it's many where clauses, not many small queries/selects/sub-selects
in the end, it generate one single query
Can you show an example of the where clauses that are different for 2 users?
it would be something like that. EF generates the query when using
ToQueryString()
but I don't have the exact query right here. It is almost the same as this one, only differences are what it bring in the select and order by statementsWhat would be the source of
dateDisabled1
and dateDisabled2
?it' the value of the variable of the line 3 of this snippet here. One for each user
I'm looking for the actual higher level use case here
Ideally in C#