How do i create the following LINQ expression dynamically? - c#

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.

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.

How to build a simple property selector expression in ef6

How can I create a property selector for entity framework like this?
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).ToLower().IndexOf(query) > -1).ToList();
}
I want the calling code to be able to be clean and simple like this:
var usernameResults = _db.Users.StandardSearchAlgorithm(u => u.Username, query);
I get a "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." error. I cannot work out how to get the expression built.
UPDATE:
Based on the answer by MBoros here is the code I ended up with. It works great.
The key to expression trees is to understand expression trees are all about breaking up what you normally write in code (like "e => e.Username.IndexOf(query)") into a series of objects: "e" gets its own object, "Username" its own object, "IndexOf()" its own object, the "query" constant its own object, and so on. The second key is to know that you can use a series of static methods on the Expression class to create various kinds of these objects, as shown below.
PropertyInfo pinfo = (PropertyInfo)((MemberExpression)property.Body).Member;
ParameterExpression parameter = Expression.Parameter(typeof(T), "e");
MemberExpression accessor = Expression.Property(parameter, pinfo);
ConstantExpression queryString = Expression.Constant(query, typeof(string));
ConstantExpression minusOne = Expression.Constant(-1, typeof(int));
MethodInfo indexOfInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) }); // easiest way to do this
Expression indexOf = Expression.Call(accessor, indexOfInfo, queryString);
Expression expression = Expression.GreaterThan(indexOf, minusOne);
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(expression, parameter);
//return predicate.Body.ToString(); // returns "e => e.Username.IndexOf(query) > -1" which is exactly what we want.
var results = queryable.Where(predicate).ToList();
return results;
Now I have a real problem, but I will ask it in a separate question. My real query looks like this:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{
return queryable.Where(e => property(e).IndexOf(query) > -1).Select(e=> new { Priority = property(e).IndexOf(query), Entity = e } ).ToList();
}
So I need to build an expression that returns an Anonymous Type!! Or even if I create a class to help, I need to write an expression that returns a new object. But I will include this in a separate question.
You cannot invoke CLR delegates so simply in sql. But you can pass in the property selector as an Expression tree., so your signature would be:
public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Expression<Func<T, string>> property, string query)
Calling would look the same. But now that you have an expression in your hand, you can have a look at this answer:
Pass expression parameter as argument to another expression
It gives you the tools to simply put an expression tree inside another one. In your case it would look like:
Expression<Func<T, bool>> predicate = e => property.AsQuote()(e).Contains(query);
predicate = predicate.ResolveQuotes();
return queryable.Where(predicate).ToList();
Once you are there, you still have the .ToLower().Contains() calls (use .Contains instead of the .IndexOf()> 1). This is actually tricky. Normally the db uses its default collation, so if it set to CI (case insensitive), then it will do the compare that way. If you don't have any constraints, and can adjust the db collation, I would go for that. In this case you can omit the .ToLower() call.
Otherwise check out this anser: https://stackoverflow.com/a/2433217/280562

How to reuse where clauses in Linq To Sql queries

I have users searching records of type Record. They type a search term in a textbox and then I search records by matching several fields with the search term.
My query looks like:
var results = from record in DataContext.Records
where
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
select record;
I have a number of queries that all use the same filter and thus I would like to extract the filtering so it can be reused. Something like:
var filter = new Func<Record, string, bool>(
(record, term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(record, term)
select record;
However, it does not work because:
Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL.
How can I reuse my where condition across queries?
You need to build an expression instead of a function:
Expression<Func<Record, bool>> filter =
record => record.Field1.ToLower().Contains(term); // rest omitted
The lambda expression remains the same, but you need to return it into a variable of type Expression<Func<Record, bool>> -- that will make the C# compiler compile it as an expression instead of a delegate, allowing it to be passed to LINQ to SQL.
However, you won't be able to use an expression variable with a C#-syntax where clause: you'll need to use the Where extension method:
var results = DataContext.Records.Where(filter);
Edited to add: If you want to be able to create filters on different terms, you just need a method to produce an expression from a term:
private static Expression<Func<Record, bool>> Filter(string term)
{
return r => r.Field1.ToLower().Contains(term);
}
var results = DataContext.Records.Where(Filter(term));
If you prefer to keep filter as a lambda as you have at the moment, you can do so, but the generics get a bit nested:
Func<string, Expression<Func<Record, bool>>> filter =
term => (r => r.Field1.ToLower().Contains(term));
var results = DataContext.Records.Where(filter(term));
Regardless, the important thing is that what goes in the Where clause must be an Expression<Func<Record, bool>> -- but as shown above you can make the expression depend on term by building a suitable expression on the fly. Which is exactly what LINQ to SQL would be doing if you spelled out the filter longhand in the Where clause.
Use a CompiledQuery!
var filter = CompiledQuery.Compile(
(DatabaseDataContext dc, Record record, string term) =>
record.Field1.ToLower().Contains(term) ||
record.Field2.ToLower().Contains(term) ||
record.Field3.ToLower().Contains(term)
);
var results = from record in DataContext.Records
where filter(DataContext, record, term)
select record;
For more information, see How to: Store and Reuse Queries.
In addition to the Expression<Func<Record, bool>> issue that others have pointed out, I suggest looking into PredicateBuilder. It's very good for dynamically combining lambda expressions.
I think you need to make it an Expression<Func<Record, bool>>. Otherwise it's trying to translate the actual C# method call to SQL rather than the description of it. This is not a guarantee that this version will work; I'm not sure which string functions are translatable to SQL.

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);

Linq to SQL DynamicInvoke(System.Object[])' has no supported translation to SQL

I have a class, Users.
Users has a UserId property.
I have a method that looks something like this:
static IQueryable<User> FilterById(this IQueryable<User> p, Func<int, bool> sel)
{
return p.Where(m => sel(m.UserId));
}
Inevitably, when I call the function:
var users = Users.FilterById(m => m > 10);
I get the following exception:
Method 'System.Object DynamicInvoke(System.Object[])' has no
supported translation to SQL.
Is there any solution to this problem? How far down the rabbit hole of Expression.KillMeAndMyFamily() might I have to go?
To clarify why I'm doing this: I'm using T4 templates to autogenerate a simple repository and a system of pipes. Within the pipes, instead of writing:
new UserPipe().Where(m => m.UserId > 10 && m.UserName.Contains("oo") && m.LastName == "Wee");
I'd like to generate something like:
new UserPipe()
.UserId(m => m > 10)
.UserName(m => m.Contains("oo"))
.LastName("Wee");
Let's take UserId as an example. You want to write:
new UserPipe().UserId(uid => uid > 10);
and want this to be the same as:
new UserPipe().Where(user => user.UserID > 10);
What you need to do is to take the expression tree of the first version and translate it to the second version.
So, first change the signature of UserId to accept an expression tree instead of a compiled lambda:
public static IQueryable<User> UserId(
IQueryable<User> source, Expression<Func<int, bool>> predicate)
Then, write a method that converts the first expression tree to the second version. Let's have a look at the two expression trees:
Input:
Lambda
uid
|
BinaryOp
>
/ \
Parameter Constant
uid 10
Output:
Lambda
user
|
BinaryOp
>
/ \
Property Constant
UserID 10
|
Parameter
user
As you can see, all you need to do is take the body of the lambda, recursively replace all occurrences of the parameter uid with the property UserIdon the parameter user and create a new lambda expression with the transformed body and the parameter user.
You can use an ExpressionVisitor to do the replacement.
Thanks to dtb, here's what I came up with:
public class ExpressionMemberMerger : ExpressionVisitor
{
MemberExpression mem;
ParameterExpression paramToReplace;
public Expression Visit<TMember, TParamType>(
Expression<Func<TParamType, bool>> exp,
Expression<Func<TMember, TParamType>> mem)
{
//get member expression
this.mem = (MemberExpression)mem.Body;
//get parameter in exp to replace
paramToReplace = exp.Parameters[0];
//replace TParamType with TMember.Param
var newExpressionBody = Visit(exp.Body);
//create lambda
return Expression.Lambda(newExpressionBody, mem.Parameters[0]);
}
protected override Expression VisitParameter(ParameterExpression p)
{
if (p == paramToReplace) return mem;
else return base.VisitParameter(p);
}
}
Now, I can convert the predicate, methinks, with something like the code below. I've done a wee bit of testing on this code; it seems to be working, but I'd be interested to hear any comments/concerns:
static IQueryable<User> FilterById(this IQueryable<User> p, Expression<Func<int, bool>> sel)
{
var merger = new ExpressionMemberMerger();
Expression<Func<User, int>> mem = m => m.UserId;
var expression = (Expression<Func<User, bool>>)merger.Visit(sel, mem);
return p.Where(expression);
}
At first glance, it looks like you're building some expressions that Linq isn't going to know how to translate into T-SQL.
I might be misunderstanding what you're trying to do, but if you want to build chain-able expressions that Linq To Sql can understand, I'd highly recommend looking at the PredicateBuilder extensions here. Even if it's not exactly what you want, understanding how it works could give you some insight on what you need to achieve under the covers to make what you're doing work.

Categories

Resources