LINQKit
LINQKit copied to clipboard
Re-Use static Expressions as filter-rules by table
Given the following data-structure I do have 3 tables.
- tblUser (holding the users of the application)
- tblRole (the available roles of the application)
- tblUserRole (the n-to-m relation between users and the roles they have)
A user can have different states, e.g. Blocked, Active and Inactive.
A tblRole-entity has therefore many tblUser entities bound.
For fetching only user of a certain stati, I've added the following to the partial of tblUser
public static Expression<Func<tblUser, bool>> IsActive => (user) => user.Status == "Active";
public static Expression<Func<tblUser, bool>> IsBlocked => (user) => user.Status == "Blocked";
public static Expression<Func<tblUser, bool>> IsInactive => (user) => user.Status == "Inactive";
Now I can query the data like:
var activeUsers = dbContext.tblUser.AsExpandable().Where(tblUser.IsActive).ToArray();
which will get me all active users actually in my database.
The problem is when doing sub-queries - reusing the same logic for filtering e.g. for active users.
var activeRolesUsers = dbContext.tblRole
.AsExpandable()
.Select(role => new
{
RoleName = role.RoleName,
ActiveRoleUsers = role.tblUserRole.Select(userRole => userRole.tblUser).Where(user => tblUser.IsActive.Invoke(user))
})
.ToArray();
Which should show me all active users in their respective role. Unfortunately, this throws me the exception:
Unable to cast object of type 'System.Linq.Expressions.PropertyExpression' to type 'System.Linq.Expressions.LambdaExpression'.
File: LinqKit.ExpressionExpander.cs Method: VisitMethodCall Line: 67
For me it would be even better to write something like:
var activeUsers = dbContext.tblUser.AsExpandable().Where(user => tblUser.IsActive.Invoke(user)).ToArray();
which would enable me to have the filterings separated and always called the same way (DRY) and they can be combined by using &&
and ||
inside the Where/Single/Any
.
Adding the following lines to TransformExpr
in starting at line 119:
var prope = input.Member as PropertyInfo;
if (( field != null && field.FieldType.GetTypeInfo().IsSubclassOf(typeof(Expression))) ||
(prope != null && prope.PropertyType.GetTypeInfo().IsSubclassOf(typeof(Expression))))
return Visit(Expression.Lambda<Func<Expression>>(input).Compile()());
now it works as expected. This code is taken from http://stackoverflow.com/questions/6226129/is-there-a-particular-reason-linqkits-expander-cant-pick-up-expressions-from-f (the last post).
Looking at the code, this is mainly the same as in TryVisitExpressionFunc
(only the check field != null
missing).
Is there any specific reason why this should not be done? If so, which one? If not, why not adding it? I know that the code is just a realy dirty hack and I'm willing to know why this feels so and if there are any reasons why it is how it is.
There is already function TryVisitExpressionFunc doing that. Doesn't it work?
@Thorium I don't have the code here to check right now. I know about the function but it seems like it is never called at ExpressionExpander.VisitMethodCall
line 63 and crashes at line 67 when casting.
So my best guess (without looking at the code or debugging) is, that field
at line 118 is not null or expression
at line 147 is not null.
Maybe there should be a check as in TryVisitExpressionFunc
checking if input.Member is PropertyInfo
? But this is also another guess as you are the one who have the big picture.
Could you try to reproduce the error/issue?
I haven't myself used LinqKit in this year at all so I'm not familiar with the latest changes.
Back in then, it needed to say var expanded = IsActive.Expand()
and then in where clause call expanded.Invoke(user)
.
You could also do SelectMany (from r in role.tblUserRole.AsExpandable() from u in dbContext.tblUser.AsExpandable()
) and then group by role? As what you probably want is to execute just one SQL clause. What you probably not want is to evaluate expressions in select to create separate user queries for each role. Keep SQL profiler running to be sure what is happening.
If there are any problems that needs to be fixed, PRs are usually accepted. :-)