I am a novice at Linq and a true beginner with expression trees.
I have a generic expression routine that builds a simple Linq where clause that I found at:
https://www.simple-talk.com/dotnet/net-framework/dynamic-linq-queries-with-expression-trees/
public Func<TSource,bool> SimpleFilter<TSource> (string property, object value)
{
var type = typeof(TSource);
var pe = Expression.Parameter(type, "p");
var propertyReference = Expression.Property(pe,property);
var constantReference = Expression.Constant(value);
var ret = Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference), new[] { pe });
return ret.Compile();
}
When I call the function as SimpleFilter("JobCustomerID", 449152) it
yields (p => p.JobCustomerId == 449152) which is correct.
If I manually place that criteria in my Linq statement, I get the correct return.
var jj = db.VW_Job_List.Where((p => p.JobCustomerId == 449152));
However when called via the filter function, the Linq throws an OutOfMemoryException.
It is called in my application as:
var jj = db.VW_Job_List.Where(SimpleFilter<VW_Job_List>("JobCustomerID", 449152));
If I call the function with a text criterion, it returns properly:
var jj = db.VW_Job_List.Where(SimpleFilter<VW_Job_List>("CompanyCode", "LCS"));
Is there something specific about using an integer variable that needs to be accommodated? Do I have something coded incorrectly? Any thoughts or insights will be appreciated.
The two calls
var jj = db.VW_Job_List.Where((p => p.JobCustomerId == 449152));
and
var jj = db.VW_Job_List.Where(SimpleFilter<VW_Job_List>("JobCustomerID", 449152));
are not equivalent. The first resolves to Queryable.Where, hence the filter is applied inside the database, while the second - to Enumerable.Where, thus causing loading the whole table in memory and applying the filter there.
The problem is that the return type of your SimpleFilter is Func<TSource, bool>. In order to make them equivalent, it should be Expression<Func<TSource, bool>>. Note that although they look visually the same, there is a huge difference between lambda expression and lambda delegate due to the different overload resolution when applied to IQueryable<T>.
So, change the method like this and try again:
public Expression<Func<TSource,bool>> SimpleFilter<TSource> (string property, object value)
{
var type = typeof(TSource);
var pe = Expression.Parameter(type, "p");
var propertyReference = Expression.Property(pe,property);
var constantReference = Expression.Constant(value);
var ret = Expression.Lambda<Func<TSource, bool>>
(Expression.Equal(propertyReference, constantReference), new[] { pe });
return ret; // No .Compile()
}
Related
I'd like to read the value of some of the properties in the expression tree some I can proceed to some calculations.
var products = db.Products
.Where(GetPredicate())
.ToList();
private Expression<Func<Product, bool>> GetPredicate()
{
ParameterExpression pe = Expression.Parameter(typeof(Product), "p");
Expression exp0 = Expression.Property(pe, "Price");
//I'd like to know the value of the 'Price'
// so I can do some calculation, then check whether
//this particular product meet the criteria...
Expression body = Expression.Constant(Result); //result is a boolean
var expr = Expression.Lambda<Func<Product, bool>>(body, new ParameterExpression[] { pe });
return expr;
}
You seem to be using a database query provider (LINQ2SQL, EF, etc.)
Before you try to use expressions to solve your problem you need to make sure the expression is understood by the query provider. In your case many of the Math methods can be converted to T-SQL valid statements. Also, if you are using Entity Framework you might want to leverage the System.Data.Objects.SqlClient.SqlFunctions class to create an expression and execute your logic on the SQL Server side against native T-SQL functions.
Now, something to understand on expression trees, is that values cannot be obtained from constructing expressions unless this is a LambdaExpression, once compiled you invoke it, in your case you can obtain the bool value.
If you need to work with the price value, you need to create more expressions representing the call to the other logic, in your example the expressions calling the static Sqrt method of the Math class.
private Expression<Func<Product, bool>> GetPredicate()
{
var pe = Expression.Parameter(typeof(Product), "p");
var price = Expression.Property(pe, "Price");
var priceDouble = Expression.Convert(price, typeof(double));
var sqrtMethod = typeof(Math).GetMethod("Sqrt");
var sqrtCall = Expression.Call(sqrtMethod, priceDouble);
var constant = Expression.Constant(4d);
var gtThan = Expression.GreaterThan(sqrtCall, constant);
var lambda = Expression.Lambda<Func<Product, bool>>(gtThan, pe);
return lambda;
}
As you can see all the logic is an expression and the provider can consume the whole expression and convert it to syntax that can be understood by the target process. The prior expression generates p => Math.Sqrt((double)p.Price) > 4d
What I want basically is to be able to to the following (following is psuedo code)
string SelectField = cb1.Name.Substring(2);
MyContext.Items.Select(x=>x.SelectField )
I tried the following:
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
var expr = Expression.Lambda(Expression.Property(pe, SelectField), pe);
query = MyContext.Items.Select(p=>expr)
but it gave me the error:
The LINQ expression node type 'Lambda' is not supported in LINQ to Entities.
Is this possible? I just want to be able to select a single entity property based on the selection of my combobox.
I was able to get my desired results using the following code (not much different then my original attempt)
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
Expression expr = Expression.Lambda(Expression.Property(pe, SelectField), pe);
Expression<Func<Item, string>> expression = (Expression<Func<Item, string>>)expr;
var query = MyContext.Items.Select(expression);
All I was missing was the Func line. It now works as I wanted.
Ok, so with this type of solution you're likely going to have to do a bunch of reflection to get it to work dynamically.
string SelectField = cb1.Name.Substring(2);
ParameterExpression pe = Expression.Parameter(typeof(Item), "p");
First you have to invoke the lambda method without knowing the complete return type. The return type is Expression<Func<Item, ?>> (with the question mark being a placeholder for the property type.
var propertyType = typeof(Item).GetProperty(SelectField).GetGetMethod().ReturnType;
var lambdaMethodParamType = typeof(Func<,>).MakeGenericType(typeof(Item), propertyType);
var lambdaMethod = typeof(Expression).GetMethods().First(x => x.Name == "Lambda" && x.IsGenericMethod).MakeGenericMethod(lambdaMethodParamType);
var expr = lambdaMethod.Invoke(null, new object[] { Expression.Property(pe, SelectField), new ParameterExpression[] { pe } });
Then we have to invoke the select method in a similar fashion because the property type isn't know until runtime.
var selectMethod = typeof(Queryable).GetMethods().First(x => x.Name == "Select").MakeGenericMethod(typeof(Item), propertyType);
var query = (IQueryable)selectMethod.Invoke(null, new object[] { MyContext.Items, expr });
I don't know what you're doing with the query variable so I can't provide any further guidance with that at this time. Again, in this example we don't know what type will be contained in the collection until runtime because it is a collection of the property values.
FYI: My method for selecting the correct lambda and select was a total hack. You really should create a more robust search method examing the signature more completely to ensure you've found the correct method you are needing. I can provide a better example later if you need it when I have more time.
I want to create a dynamic linq expression for sql IN clause in EF 6.0 with code first approch. Note that i am new to Expressions. What i want to achive is
select * from Courses where CourseId in (1, 2, 3, 4)
//CourseId is integer
The normal linq query looks like this. But i want to query it dynamically
string[] ids = new string[]{"1", "2", "3", "4"};
var courselist = DBEntities.Courses.Where(c => ids.Contains(SqlFunctions.StringConvert((decimal?)c.CourseId)))
There are two ways to make dynamic expression.
1) one ways is to loop through ids and make expressions
The below code will create the following expression in debug view
{f => ((StringConvert(Convert(f.CourseId)).Equals("23") Or StringConvert(Convert(f.CourseId)).Equals("2")) Or StringConvert(Convert(f.CourseId)).Equals("1"))}
Dynamic Expression is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
MethodInfo mi = null;
MethodCallExpression mce = null;
if (property.Type == typeof(int))
{
var castProperty = Expression.Convert(property, typeof(double?));
var t = Expression.Parameter(typeof(SqlFunctions), "SqlFunctions");
mi = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
mce = Expression.Call(null,mi, castProperty);
}
mi = typeof(string).GetMethod("Equals", new Type[]{ typeof(string)});
BinaryExpression bex = null;
if (values.Length <= 1)
{
return Expression.Lambda<Func<T, bool>>(Expression.Call(mce, mi, Expression.Constant(values[0]), param));
}
//var exp1 = Expression.Call(mce, mi, Expression.Constant(values[0]));
for (int i = 0; i < values.Length; i++)
{
if (bex == null)
{
bex = Expression.Or(Expression.Call(mce, mi, Expression.Constant(values[i])), Expression.Call(mce, mi, Expression.Constant(values[i + 1])));
i++;
}
else
bex = Expression.Or(bex, Expression.Call(mce, mi, Expression.Constant(values[i])));
}//End of for loop
return Expression.Lambda<Func<T, bool>>(bex, param);
2) The 2nd way that i tried (debug view)
{f => val.Contains("23")} //val is parameter of values above
The dynamic expression for above that i tried is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
var micontain = typeof(Enumerable).GetMethods().Where(m => m.Name == "Contains" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var mc = Expression.Call(micontain, Expression.Parameter(values.GetType(), "val"), Expression.Constant("2"));//NOTE: I haven't use CourseId for now as i am getting conversion error
return Expression.Lambda<Func<T, bool>>(mc, param);
I get the following errors
LINQ to Entities does not recognize the method 'System.String StringConvert(System.Nullable`1[System.Double])' method, and this
method cannot be translated into a store expression when i use the
first methodology. I know i can't use ToString with EF thats why I used SqlFunctions but it is not working for me.
The parameter 'val' was not bound in the specified LINQ to Entities query expression using 2nd methodology
I am trying this from last 4 days. I googled it but didn't find any suitable solution. Please help me.
After a lot of struggle I found solution to my question.
I want to achieve this sql query
select * from Courses where CourseId in (1, 2, 3, 4)
Using Linq to Entities, but I want to pass in(1,2,3,4) list dynamically to linq query. I created an Extension class for that purpose.
public static class LinqExtensions
{
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> In<T, TValue>(this Expression<Func<T, bool>> predicate,string propertyName, List<TValue> values)
{
var param = predicate.Parameters.Single();
MemberExpression property = Expression.PropertyOrField(param, propertyName);
var micontain = typeof(List<TValue>).GetMethod("Contains");
var mc = Expression.Call(Expression.Constant(values), micontain, property);
return Expression.Lambda<Func<T, bool>>(mc, param);
}
}
Use of LinqExtensions
var pred = LinqExtensions.False<Course>(); //You can chain In function like LinqExtensions.False<Course>().In<Course, int>("CourseId", inList);
var inList= new List<int>(){1, 2, 3}; //Keep in mind the list must be of same type of the Property that will be compared with. In my case CourseId is integer so the in List have integer values
pred =pred.In<Course, int>("CourseId", inList); //TValue is int. As CourseId is of type int.
var data = MyEntities.Courses.Where(pred);
I hope this might be beneficial for some one
have you seen the type of
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId)))
above statement would not return actual list of courses. The query is not executed yet. It just returns IQuereable. The query is executed when you actually call .ToList() method on it
so, your solution is..
Create array of IDs using for loop and then simply run the below query
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId))).ToList()
The following is the code:
list = query.Where(x => ((string)x.GetType()
.GetProperty(propertyName).GetValue(this, null))
.EndsWith(filterValue)).ToList();
This code is a refactored code that if I break it to its object and property level it would take me repeating it hundred times all around my code project. According to my research in this site it has something to do with the SQL translation. But is there any work around where this code or its variant will work fine?
The solution is to create an Expression that returns the specified property and pass that expression to Where:
var query = session.Query<YourType>();
list = query.Where(GetExpression<YourType>(propertyName, filterValue)).ToList();
GetExpression looks something like this:
public static Expression<Func<T, bool>> GetExpression<T>(string propertyName,
string filterValue)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
var method = typeof(string).GetMethod("EndsWith", new [] { typeof(string) });
var body = Expression.Call(property, method,
Expression.Constant(filterValue));
return (Expression<Func<T, bool>>)Expression.Lambda(body, parameter);
}
This is the code I need to alter:
var xParam = Expression.Parameter(typeof(E), typeof(E).Name);
MemberExpression leftExpr = MemberExpression.Property(xParam, this._KeyProperty);
Expression rightExpr = Expression.Constant(id);
BinaryExpression binaryExpr = MemberExpression.Equal(leftExpr, rightExpr);
//Create Lambda Expression for the selection
Expression<Func<E, bool>> lambdaExpr = Expression.Lambda<Func<E, bool>>(binaryExpr, new ParameterExpression[] { xParam });
Right now the expression I'm getting out of this is (x => x.RowId == id) and what I want to change it to is (x => x.RowId) so that I can use it in an OrderBy for the ObjectContext.CreateQuery(T) method called later on.
Does anyone know how to change the above code so the lambda is correct to use in an OrderBy to order by the ID field?
Side Notes: The RowId is coming from this._KeyProperty I believe. This is part of a generic repository using the entity framework on Asp.Net MVC
Just omit creating the constant and "=":
var xParam = Expression.Parameter(typeof(E), "x");
var propertyAccessExpr = Expression.Property(xParam, this._KeyProperty);
var lambdaExpr = Expression.Lambda<Func<E, bool>>(propertyAccessExpr, xParam);
This assumes that _KeyProperty has type 'bool'. If it has a different type, just change Func<E, bool> to the appropriate type.
(Edited to incorporate asgerhallas and LukLed's good suggestions)