Combining expression trees - c#

I have the following expression:
public Expression<Func<T, bool>> UserAccessCheckExpression<T>(int userId) where T : class
{
return x => (IsAdmin || userId == CurrentUserId || userId == 0);
}
Then I want to apply this filter to several collections (IQueryable) like this one:
return tasks
.Where(t => t.TaskUsers
.Any(x => UserAccessCheckExpression<TaskUser>(x.User) && x.SomeBool == true));
I'm getting the following error while doing so:
Error 40 Cannot implicitly convert type System.Linq.Expressions.Expression<System.Func<TaskUser,bool>> to bool
I can't use workaround with interface inheritance (like TaskUser inherits interface with int UserId property (where T : IHasUserId)) since I want to combine logic.

The problem is that your UserAccessCheckExpression() method is returning an Expression while the Any() method is expecting a boolean.
Now, you can get your code to compile by compiling the Expression and invoking the method (using UserAccessCheckExpression<TaskUser>(x.User).Compile().Invoke(x.User)) but that would obviously fail on runtime because Linq-to-Entities wouldn't be able to translate your Any() to a store query as it no longer contains an Expression.
LinqKit is aiming to solve this problem using its own Invoke extension method that while letting your code compile, will make sure your Expression will get replaced back to its original form using another extension method named AsExpandable() that is extending the entity set.
Try this:
using LinqKit.Extensions;
return tasks
.AsExpandable()
.Where(t => t.TaskUsers.Any(
x => UserAccessCheckExpression<TaskUser>(x.User).Invoke(x)
&& x.SomeBool == true));
More on LinqKit

Yeah, so, you can't do that. There's a difference between an Expression<> and a Func<>. You're trying to use the UserAccessCheckExpression as a func. I'm not sure what you're trying to do, but you can compile it to a func and then use it sorta like you are:
var expr = UserAccessCheckExpression<TaskUser>(x.User);
var func = expr.Compile();
// Later use it like ...
var result = func();
But I expect you're using this with EF or Linq2Sql? That being the case you'll need to rewrite the expression. It can be done by hand (not easy) or, better, use a tool like PredicateBuilder.

Related

Create generic .Any extention method for IQueryable [duplicate]

I'm building a SQL "WHERE" clause dynamically using the System.Linq.Expressions.Expression class. It works well for simple clauses, e.g. to add "PhaseCode = X" clause, I do the following:
var equalTarget = Expression.Constant(phaseCode, typeof(int?));
var phaseEquals = Expression.Equal(Expression.PropertyOrField(projParam, "PhaseCode"), equalTarget);
However, now I'm trying to build an expression that will return the record if a project has been assigned to a particular group. Project and Group has many-to-many relationship.
Without the expression trees, I would do it as follows:
db.Projects.Where(p => .... && p.GroupsAssigned.Any(g => g.ID == groupId))
However, I can't seem to find a way to express that with the Expression class.
There are actually two things I can't figure out:
How to traverse the relationships between tables
How to do x.Any()
Any help is greatly appreciated.
Calling an extension method, like Enumerable.Any or Queryable.Any, is simply a static method call on the sequence and the lambda expression you created for the WHERE clause. You can use Expression.Call to do this:
// for Enumerable.Any<T>(IEnumerable<T>,Predicate<T>)
var overload = typeof(Enumerable).GetMethods("Any")
.Single(mi => mi.GetParameters().Count() == 2);
var call = Expression.Call(
overload,
Expression.PropertyOrField(projParam, "GroupsAssigned"),
anyLambda);
For Queryable.Any<T>, you'll need to roll this up into a method:
static Expression BuildAny<TSource>(Expression<Func<TSource, bool>> predicate)
{
var overload = typeof(Queryable).GetMethods("Any")
.Single(mi => mi.GetParameters().Count() == 2);
var call = Expression.Call(
overload,
Expression.PropertyOrField(projParam, "GroupsAssigned"),
predicate);
return call;
}
Although this seems odd that you're unable to do this through a normal query.

LINQ dynamic expression in order by clause

I have the following query:
product = product.OrderByDescending(d => d.ProductAttributeItem
.Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
.SelectMany(p => p.AttributeItem.AttributeItemValue)
.Any(o => EF.Functions.Like(o.Value, "%apple%") || EF.Functions.Like(o.Value, "%samsung%"))
which in fact is working perfectly fine. product is a rather complex query built with many predicates based on input filters. Here we are interested in the Any() part of the query, specifically the predicate part - how EF.Functions.Like(o.Value, "%apple%") || EF.Functions.Like(o.Value, "%samsung%") can be generated dynamically.
I am already using some predicate builder extension methods in our project and they are working great for non nested cases like:
var condition = PredicateBuilder.True<AttributeItemValue>();
if(filters.OnlyActivated)
condition = condition.And(product => product.IsActive);
product = _context.Product.Where(condition);
So I have tried to build the predicate in loop:
var aivCond = PredicateBuilder.True<AttributeItemValue>();
foreach (var s in searchQueryArray)
{
aivCond = aivCond.Or(f =>
EF.Functions.Like(f.Value, "%" + s + "%"));
}
So now the aivCond is of type Expression<Func<AttributItemValue, bool> but this can't be used to replace the lambda in Any() because it expects Func<TSource, bool>. Tried to compile it this way aivCond.Compile() but the following error occurs:
System.ArgumentException: Expression of type 'System.Func`2[AttributeItemValue,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[AttributeItemValue,System.Boolean]]' of method 'Boolean Any[AttributeItemValue](System.Linq.IQueryable`1[AttributeItemValue]
I have also tried to build the lambda expression from string:
var filter = "f => EF.Functions.Like(f.Value, \"%apple%\") || f => EF.Functions.Like(f.Value, \"%samsung%\")";
var options = ScriptOptions.Default
.AddReferences(typeof(AttributeItemValue).Assembly)
.AddReferences(typeof(Microsoft.EntityFrameworkCore.EF).Assembly)
.AddReferences(typeof(DbFunctions).Assembly)
.AddImports("Microsoft.EntityFrameworkCore");
Func<AttributeItemValue, bool> filterExpression = await CSharpScript.EvaluateAsync<Func<AttributeItemValue, bool>>(filter, options);
with no luck.
I know I missing knowledge for expression trees, compiling and invocation for delegates so any help(and explanation) would be really appreciated!
EDIT / SOLUTION
So there is a solution thanks to the help of Richard Deeming.
He was right for starting the predicate with False. I stupidly copy/pasted code from a different method without noticing.
As about his second comment, adding AsQueryable() allows to pass an Expression<Func<TSource, bool>> which is pretty obvious but not for me. This translates fine and the compiler is ok.
Anyway, there is another approach, using LINQKit's AsExpandble() method and the inbuilt Compile() method in the Expression class and as described in LINQKit:
Compile is an inbuilt method in the Expression class. It converts the Expression<Func<Purchase,bool> into a plain Func<Purchase,bool> which satisfies the compiler. Of course, if this method actually ran, we'd end up with compiled IL code instead of an expression tree, and LINQ to SQL or Entity Framework would throw an exception. But here's the clever part: Compile never actually runs; nor does LINQ to SQL or Entity Framework ever get to see it. The call to Compile gets stripped out entirely by a special wrapper that was created by calling AsExpandable, and substituted for a correct expression tree.
So the code would look like this:
product = product.AsExpandable().OrderBy(d => d.ProductAttributeItem
.Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
.SelectMany(p => p.AttributeItem.AttributeItemValue)
.Any(aivCond.Compile()));
Thank you everyone for the help and I hope this question helps somebody lost in the code... !
As mentioned in the comments, you just need to use the AsQueryable method on the collection to pass in the Expression<Func<TItem, bool>> as the filter.
product = product.OrderByDescending(d => d.ProductAttributeItem
.Where(ai => ai.AttributeItem.AttributeId == (int)DefaultAttributes.Name)
.SelectMany(p => p.AttributeItem.AttributeItemValue)
.AsQueryable().Any(aivCond);

Combining two different PredicateBuilders with Expressions

I am having a predicate builder and it is working fine
var filter = sortKeys.Aggregate(filter, (currentFilter, sortkey) => currentFilter.Or(
x => x.Appointments.Any(y => y.RowStatus == Constants.CurrentRowStatus )));
I am now trying to split the conditions which is inside the appointment into another predicate builder so that I can add conditions on the go and reuse the function.
I had tried creating an expression and then using it in the main predicate builder but it is failing
private static Expression<Func<Appointment, bool>> TmpApt(string status)
{
var predicate = PredicateBuilder.False<Appointment>();
predicate = predicate.Or(p => p.RowStatus == status);
return predicate;
}
Changed main predicate to use the above expression
var filter = sortKeys.Aggregate(PredicateBuilder.True<Person>(), (current, s) =>
current.Or(x => x.Appointments.Any(TmpApt(s))));
It showing an error that
Argument type 'System.Linq.Expressions.Expression<System.Func<Appointment,bool>>' is
not assignable to parameter type System.Func<Appointment,bool>
I had even tried LinqKit extension method like Expand but could find a solution.
had also tried Reusable predicate expressions in LINQ, then it is not showing any errors while compiling but when on the application side, it is showing
Unsupported overload used for query operator 'Any'.
Can anyone please help me how to resolve the error or suggest an alternative solution.
You can use LINQKit to invoke the expression that you have at the location that you want to be using it:
var predicate = TmpApt();
var filter = sortKeys.Aggregate(PredicateBuilder.False<Person>(),
(current, s) => current.Or(x =>
x.Appointments.Any(appointment => predicate.Invoke(appointment))))
.Expand();
Note that you'll need to pull TmpApt out into a variable for LINQKit to successfully evaluate it, due to a bug in its implementation.
Also note that you'll want to initialize the aggregate operation to False, because True OR-ed with anything is true.
Also note that you can simplify the implementation of TmpApt to just the following:
private static Expression<Func<Appointment, bool>> TmpApt()
{
return p => p.RowStatus == Constants.CurrentRowStatus;
}
There's no need to use a predicate builder to Or it with False here.

Passing Parameters into an Expression Specification using LinqToSQL

I want to reduce duplicate logic in a LinqToSQL query by using Expression<Func<T,bool>>. We've successfully done the before using static properties like so:
public static Expression<Func<Document, bool>> IsActive
{
get
{
return document => !document.Deleted;
}
}
...
_workspace.GetDataSource<Document>().Where(DocumentSpecifications.IsActive)
However I am struggling to get this working when additional parameters need to be passed into the Expression like so:
public static Expression<Func<Comment, bool>> IsUnread(int userId, Viewed viewed)
{
return
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > viewed.LastViewedDate);
}
...
// Throwing "Argument type 'System.Linq.Expression<X,bool>' is not assignable
// to parameter type 'System.Func<X,bool>'"
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView))
})
How do I convert the specification so it can be accepted in this way and would it still convert to SQL correctly?
EDIT: Following Anders advice I added .Compile() to the query. It now works correctly when unit testing in memory collections; however when LinqToSQL trys to convert it into SQL I get the following exception:
System.NotSupportedException: Unsupported overload used for query operator 'Count'
I've tried:
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
a.Comments.AsQueryable().Count(CommentSpecifications.IsUnread(actorId, a.LastView))
It looks like the second query is executed as linq-to-objects and not as linq-to-sql. It expects a Func<X, bool> which is what linq-to-objects use, while linq-to-sql (or any other IQueryable provider) expects an uncompiled expression tree that can be translated to something else)
A quick fix is to call Compile() on the expression to convert it to an executable function.
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
To be more detailed you really should figure out why that query is executed as linq-to-objects and not linq-to-sql. Especially if you expected it to be translated to efficient sql it could become a performance nightmare.
Update
After your edit it's more obvious what's happening:
You're running the query as linq-to-objects during unit testing and as linq-to-sql later. In that case converting the expression to a Func<> through Compile() won't work as linq-to-sql won't recognize it.
Update 2
Composing reusable part into query expression that are to be translated is hard - it confuses the translation engine. Linq-to-sql is somewhat more tolerant than linq-to-entities is, but it is nevertheless hard to get it work. A better way is often to make chaining functions that operate on IQueryable<T>.
public static IQueryable<Comment> WhereIsUnread(this IQueryable<Comment> src, int userId)
{
return src.Where(
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > c.Alert.LastView.LastViewedDate));
}
...
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.WhereIsUnRead(actorId).Count()
})
Something like that should work. Notice I've rewritten how the last viewed date is accessed, as it would otherwise fail translation to SQL when passed in as a parameter.

Multiple LINQ expressions, and dynamic properties

1.) I have an entity query like: return myEntityStore.Query<theType>(x => x.Name == "whatevs");
However, ideally I want to call a function which appends other expressions to the original query, like OrderBy's and more Where's.
Something like this:
public IQueryable<T> ProcessQuery<T>(System.Linq.Expressions.Expression<Func<T, bool>> theOriginalQuery) where T: class
{
return EntityStore.Query<T>(theOriginalQuery).Where(x => x.Active == true).OrderBy(x => x.OrderBy);
}
2.) One of the obvious problems here is that I'm working with <T>, so is there a way to specify the property as a string or something?
<T>(expression).OrderBy(x => "x.ThisColumnExistsIPromise");
3.) The Query function already transforms the expression into a Where(), so would it be sufficient to simply do a Where(expression).Where(expression)?
So, at the end of the day, is the below possible to achieve?
entityStore.Query<T>(originalExpression).Where(additionalExpression).Where(x => "x.Active == true").OrderBy(x => "x.OrderBy");
Actually the question isn't so clear. But I noticed that your problem might be solved if you used LINQ Dynamic Query Library then you could use Property name as string to do whatever you want (OrderBy, Where ....)
For more information look at this

Categories

Resources