I have a property on a class
Expression<Func<Product, int>> predicate;
that will be assigned to different expressions throughout the application.
In a method called GetProducts I would like to retrieve Products from the DB using Entity Framework, using this predicate. I will then have a variable called myInt that I would like to use as the int parameter, that will then be assigned a value.
So I tried
dbContext.Products.Where(p => predicate(p, myInt))
but I got Non-invocable member 'predicate' cannot be used like a method. error.
It looks like I need to do some expression tree manipulation, to create a new expression tree, with myInt baked in it. How can I do this?
Thank you for your help.
You defined predicate as a Type but you are using it as a method.
You can define a predicate in the following way:
private bool predicate(Product product, int myInt)
{
//put your logic here
return true;
}
You can also use lambda expressions:
product => product.SomeValue > 5
Edit:
Expression<Func<Product, int>> predicate = (product,val) => product.SomeValue > val;
var filtered = dbContext.Products.Where(predicate);
Avoid using types when naming a parameters (i.e don't name an integer MyInt)
OK, got it.
ParameterExpression prm = Expression.Parameter(typeof(Product));
InvocationExpression inv = Expression.Invoke(predicate, prm, Expression.Constant(myInt));
var lambda = (Expression<Func<Product,bool>>) Expression.Lambda(inv, prm);
dbContext.Products.Where(lambda);
The key is Expression.Invoke that can invoke the predicate, using supplied parameters.
EDIT - After trying it, this only works with linq2sql - with Entity Framework it can't translate InvocationExpression to SQL. Instead, I used LinqKit, as follows:
dbContext.Products.AsExpandable().Where(p => predicate.Invoke(p, myInt))
Related
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 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
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.
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);
I have the following code:
public OTestTable GetTestCode(Func<TestTable, bool> whereClause)
{
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
CoreContext is my data context (which is initialized in a base class)
My TestTableMap is as follows:
public class TestTableMap
{
public static readonly Func<TestTable, OTestTable> DataToObject = mapper =>
new OTestTable
{
Code = mapper.mycode
};
}
Then in my business method i have the following:
public OTestTable GetTestCode(string code)
{
return QueryEngine.GetTestCode(id => id.mycode == code);
}
From my main program, i am calling GetTestCode with a string value.
When I watch SQL profiler, I get the following:
SELECT [t0].[mycode]
FROM [dbo].[TestTable] AS [t0]
It does not have the where clause appended to the SQL query. If i add the where clause to the LINQ as var query = from c in DataContext.TestTable where c.mycode == '' select c;
It will add the where clause.
However, when I run my code, it will return the correct record, but it seems like I am pulling back all records from the database and filtering in my code (which should not happen).
Any thoughts with what I am doing wrong?
Thanks
In order to construct SQL statements, LINQ to SQL requires an expression tree. Func<TestTable, bool> does not represent an expression tree, it is a "black box" function pointer. LINQ cannot do anything intelligent with this apart from blindly execute it on an in-memory collection.
You need to do this instead:
public OTestTable GetTestCode(Expression<Func<TestTable, bool>> whereClause) {
return CoreContext.TestTables.Where(whereClause).Select(TestTableMap.DataToObject).FirstOrDefault();
}
This code compiles using the Queryable.Where extension method, which does accept an expression tree, rather than the Enumerable.Where extension method, which only accepts a raw delegate.
Try creating your where clause as:
Expression<Func<T, bool>> whereClause
Where the T parameter is your source type Table<T> source
Also see the PredicateBuilder here: http://www.albahari.com/nutshell/predicatebuilder.aspx
It provides you convenient extension methods to predicate IQueryable<T>. like this:
var predicate = PredicateBuilder.True<Family>();
predicate = predicate.And(o => o.Birthday < new DateTime(1980, 1, 1));
.Or(o => o.Name.Contains("ke"));
var result = Source.Where(predicate).ToList();