Create generic .Any extention method for IQueryable [duplicate] - c#

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.

Related

Combining expression trees

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.

Reuse of a LINQ query

This is not about the reuse of a result but more the statement itself.
Nor is it about an error when using var as mentioned in: LINQ to SQL: Reuse lambda expression
Out of sheer curiosity I was wondering if it is possible to reuse a single LINQ statement.
Lets say I have the following LINQ statement:
.Where(x => x.Contains(""));
Is it possible to extract the statement x => x.Contains("") and use some kind of reference to this for later usage in, lets say, another class?
So I can call it like: .Where(previouslySavedStatement);
You can store it in a variable. If you are working with IQueryable then use:
System.Linq.Expressions.Expression<Func<Foo, bool>> selector = x => x.Contains("");
If you are using IEnumerable then use:
Func<Foo, bool> selector = x => x.Contains("");
And use it in your query:
query.Where(selector);
Yes, you can write a function containing the query you want to reuse, which takes and returns an IQueryable<T>
public IQueryable<T> ContainsEmpty(IQueryable<T> query)
{
return query.Where(x => x.Contains(""));
}
Now you can reuse it:
query1 = ContainsEmpty(query1);
query2 = ContainsEmpty(another);
It depends. There's two Where methods, Enumerable.Where and Queryable.Where. If you're applying the .Where to an IEnumerable than the first one is called, if you're applying it to an IQueryable the second one is called.
Since Enumerable.Where takes in a Func, it isn't reusable. Since Queryable.Where takes in an expression, it is reusable. You can do so as follows:
var x = new List<string>().AsQueryable();
var query = x.Where (n => n.Contains("some string"));
//Extract the lambda clause
var expr = query.Expression;
var methodExpr = (MethodCallExpression)expr;
var quoteExpr = (UnaryExpression)methodExpr.Arguments[1];
var funcExpr = (Expression<Func<string, bool>>)quoteExpr.Operand;
You can then later re-apply the where expression:
var query2 = x.Where(funcExpr);
I wrote a library to address exactly this concern, it's called CLinq and you can find an implementation for the EntityFramework here: https://www.nuget.org/packages/CLinq.EntityFramework
It allows to create query snippets and use them everywhere you in a linq query. Following the example of Hamid, create the following expression:
System.Linq.Expressions.Expression<Func<Foo, bool>> selector = x => x.Contains("");
You can now use this query everywhere in your linq queries like this:
query.AsComposable().Where(o => selector.Pass(o));
Additionally to this simple example you're also able to combine your query snippets:
query.AsComposable().Where(o => selector.Pass(o) || anotherSelector.Pass(o));
or even merge them together:
query.AsComposable().Where(o => anotherSelector.Pass(selector.Pass(o)));
There's some more features, but I think it's really helpful, so check it out :)

Build expression tree for LINQ using List<T>.Contains method

Problem
I'm working on refactoring some LINQ queries for several reports in our web application, and I'm attempting to move some duplicate query predicates into their own IQueryable exension methods so we can reuse them for these reports, and reports in the future. As you can probably infer, I've already refactored the predicate for groups, but the predicate for codes is giving me problems. This is an example of one of the report methods I have so far:
DAL method:
public List<Entities.QueryView> GetQueryView(Filter filter)
{
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
IQueryable Extension:
public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
if (codes.Count > 0)
return query.Where(Predicates.FilterByCode<T>(codes));
return query;
}
Predicate:
public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
// Method info for List<string>.Contains(code).
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
// List of codes to call .Contains() against.
var instance = Expression.Variable(typeof(List<string>), "codes");
var param = Expression.Parameter(typeof(T), "j");
var left = Expression.Property(param, "Code");
var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}
The problem I'm having is that Queryable.Where doesn't accept a type of Expression<Func<T, List<string>, bool>. The only way I can think of creating this predicate dynamically is to use two parameters, which is the part that is really stumping me.
What I'm not comprehending is the following method works. I can pass the exact lambda expression I am trying to create dynamically, and it correctly filters my data.
public List<Entities.QueryView> GetQueryView(Filter filter)
{
// Get the codes here.
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.Where(p => codes.Contains(p.Code)) // This works fine.
//.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
Questions
Can I implement my own Queryable.Where overload? If so, is it even feasible?
If an overload isn't feasible, is there a way to dynamically construct the predicate p => codes.Contains(p.Code) without using two parameters?
Is there an easier way to do this? I feel like I'm missing something.
You can create your own extension method, name it Where, accept an IQueryable<T>, return an IQueryable<T>, and otherwise make it emulate the form of LINQ methods. It wouldn't be a LINQ method, but it would look like one. I would discourage you from writing such a method simply because it would likely confuse others; even if you want to make a new extension method, use a name not used in LINQ to avoid confusion. In short, do what you're doing now, create new extensions without actually naming them Where. If you really wanted to name one Where though nothing's stopping you.
Sure, just use a lambda:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
where T : ICoded //some interface with a `Code` field
{
return p => codes.Contains(p.Code);
}
If you really cannot have your entities implement an interface (hint: you almost certainly can), then the code would look identical to the code that you have, but using the list that you pass in as a constant rather than a new parameter:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
{
var methodInfo = typeof(List<string>).GetMethod("Contains",
new Type[] { typeof(string) });
var list = Expression.Constant(codes);
var param = Expression.Parameter(typeof(T), "j");
var value = Expression.Property(param, "Code");
var body = Expression.Call(list, methodInfo, value);
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, bool>>(body, param);
}
I would strongly encourage use of the former method; this method loses static type safety, and is more complex and as such harder to maintain.
Another note, the comment you have in your code: // j => codes.Contains(j.Code) isn't accurate. What that lambda actually looks like is: (j, codes) => codes.Contains(j.Code); which is actually noticeably different.
See the first half of #2.

How do i create the following LINQ expression dynamically?

I need the following C# code to be translated to a valid Entity Framework 6 expression:
(f => f.GetType().GetProperty(stringParamter).GetValue(f).ToString() == anotherStringParameter)
This guy did it for the "Order By" part, but i cant seem to figure it out for the "where" part...
Generically speaking what i am trying to achieve here is a form of dynamic query where the user will "pick" properties to filter in a "dropbox", supply the filter-value and hit query... usually people do like f => f.TargetProp == userValue but i can't do that when i dont know which one it is...
You need to construct the expression tree that represents the access to the property:
public static Expression<Func<T, bool>> PropertyEquals<T>(
string propertyName, string valueToCompare)
{
var param = Expression.Parameter(typeof(T));
var body = Expression.Equal(Expression.Property(param, propertyName)
, Expression.Constant(valueToCompare));
return Expression.Lambda<Func<T, bool>>(body, param);
}
This allows you to write:
query = query.Where(PropertyEquals<EntityType>(stringParameter, anotherString));
Have you considered using the Dynamic Link Library? It allows you to compose expressions as strings instead of lambda expressions.
Examples:
var query = baseQuery.Where("Id=5");
var query = baseQuery.Where("Id=#0", 5);
I've been keeping an updated version of Microsoft's Dynamic Linq example at https://github.com/NArnott/System.Linq.Dynamic in case you are interested, and it's also available on NuGet.

Using a member access lambda expression to parametrise a LINQ to SQL predicate

I have a query that needs to be reused all over the place and I need to vary which property/column gets used for a join.
What I'd like to be able to do is something like:
query = RestrictByProp(query, x=>x.ID);
An extremely simplified RestrictByProp() could be*:
private static IQueryable<Role> RestrictByProp(IQueryable<Role> query,
Func<Role, int> selector)
{
return query.Where(x => selector(x) == 1);
}
The problem is that even this simple implementation causes a runtime exception:
Method 'System.Object DynamicInvoke(System.Object[])' has no
supported translation to SQL.
**(Here I'm just adding a simple 'where' clause - in my real code I'd be using the lambda to pick which property to use for a join).*
I find this strange because if the member access lambda is done inline it is fine:
private static IQueryable<Role> RestrictByID(IQueryable<Role> query)
{
return query.Where(x=> x.ID == 1);
}
LINQ to SQL is also happy if you pass in an Expression<Func<Role, bool>> (i.e. when the parameter is x=>x.ID == 1) but that defeats the object because I need the value of the right-hand operand to be determined within the query.
Is there a way to somehow munge the lambda expression in RestrictByProp() so that LINQ to SQL knows how to generate the SQL?
First, you need to change your method signature:
private static IQueryable<Role> RestrictByProp(IQueryable<Role> query,
Expression<Func<Role, int>> selector)
That will mean your lambda expression is converted into an expression tree instead of a delegate.
You'll then need to build an Expression<Func<Role, bool>> from the existing expression tree.
It will look something like this:
LambdaExpression lambda = (LambdaExpression) selector;
var predicate = Expression.Equal(selector, Expression.Constant(1));
var lambdaPredicate = Expression.Lambda<Func<Role, bool>>(predicate,
lambda.Parameters);
return query.Where(lambdaPredicate);

Categories

Resources