I'm studying "Expression Tree" but I'm not managing to perform these expressions:
// first case
someList.Select(p => p.SomeProperty);
and
// second case
someList.Select(p => new OtherClass
{
SomeProperty = p.SomeProperty
})
To the "first case" I tried do this:
var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });
var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");
Expression.Call(
typeof(Enumerable),
"Select",
new Type[]
{
typeof(SomeClass),
typeof(string)
},
Expression.Lambda(
someProperty,
someParam
)
).Dump();
But I get this error:
InvalidOperationException: No generic method 'Select' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
About the "second case", I don't have ideia how to proceed.
Can anyone guide me here?
Some examples of what you could do:
Given
public class SomeClass
{
public string SomeProperty { get; set; }
}
and
var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });
var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");
Expression<Func<SomeClass, string>> lambda = Expression.Lambda<Func<SomeClass, string>>(someProperty, someParam); // p => p.SomeProperty
Using an IEnumerable<SomeClass>... Note the .Compile()
Func<SomeClass, string> compiled = lambda.Compile();
IEnumerable<string> q1 = someList.Select(compiled);
You shouldn't ever use AsQueryable() but in unit tests and "experimentation" programs (like this one). Just to make #Peter happy, I'll add another possible condition: if you really know what it does (not think you know what it does, really!), then you can use it. But if it the first time you use it, I still suggest you ask on SO if you are right in using it.
IQueryable<SomeClass> queryable = someList.AsQueryable();
Directly using the Queryable.Select()
IQueryable<string> q2 = queryable.Select(lambda);
Building a Select and using the CreateQuery (this is very similar to what internally the Queryable.Select does) to "inject" it in the query.
MethodInfo select = (from x in typeof(Queryable).GetMethods()
where x.Name == "Select" && x.IsGenericMethod
let gens = x.GetGenericArguments()
where gens.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(gens[0]) &&
pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(gens))
select x).Single().MakeGenericMethod(typeof(SomeClass), typeof(string));
MethodCallExpression select2 = Expression.Call(null, select, Expression.Constant(queryable), lambda);
IQueryProvider provider = queryable.Provider;
IQueryable<string> q3 = provider.CreateQuery<string>(select2);
Calm down folks, after some research I found what was missing in my code...
On the fist case:
Expression.Call(
typeof(Enumerable),
"Select",
new Type[]
{
typeof(SomeClass),
typeof(string)
},
Expression.Constant(someList), // <---------------- HERE IT IS
Expression.Lambda(
someProperty,
someParam
)
);
To the second case, I created the "new" expression through the code below:
var bind = Expression.Bind(typeof(OtherClass).GetProperty("SomeProperty"), someProperty);
var otherClassNew = Expression.New(typeof(OtherClass));
var otherClassInit = Expression.MemberInit(otherClassNew, bind);
Anyway, Thank you all for your help!
Related
I have a linq query which is as follows:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select(lambda)
.ToList();
I use lambda because i have a variable in my select statement. The lambda:
var wantedData = "Adress";
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, wantedData);
var lambda = Expression.Lambda<Func<Person, Adress>>(body, x);
The query will return the addresses for person with id 123. Let's say instead of the addresses I would like to recieve all subscriptions this person has. Only setting wantedData = "Subscription" will not work, because of the "Adress" in the lambda statement. So I'm looking for a way to use a variable to change "Adress" in the lambda statement.
I tried the following, which obviously does not work.
var lambda = Expression.Lambda<Func<Person, wantedData>>(body, x);
Is there a way to do this?
Ok, I managed to create a generic query method that takes an Expression as parameter.
EDIT: This will receive a string value and will try to match it with an existing property on your Person class.
private IEnumerable<TResult> GetPersonData<TResult>(Expression<Func<Person, TResult>> selectExpression)
{
return _dbContext.Persons
// .Where(filter)
.Select(selectExpression)
.ToList();
}
public IEnumerable<object> GetData(string dataPropertyName)
{
switch(dataPropertyName) {
case nameof(Person.Address): return GetPersonData(p => p.Address);
case nameof(Person.Subscription): return GetPersonData(p => p.Subscription);
// other cases
default: throw new InvalidArgumentException("Invalid property name");
}
}
note that this code is just an example written on the spot and it might not work directly with copy-paste
I've made something similar to an OrderBy and tweak it a little for your select. This only works if you know the return type.
You can create an extension method with the following code:
public static IQueryable<TResult> Select<T,TResult>(this IQueryable<T> source, string propertyName)
{
var prop = typeof(T).GetProperties()
.FirstOrDefault(x => x.Name.ToUpper() == propertyName.ToUpper());
ParameterExpression pe = Expression.Parameter(typeof(T), "x");
MemberExpression body = Expression.Property(pe, prop.Name);
LambdaExpression lambda = Expression.Lambda(body, new ParameterExpression[] { pe });
MethodCallExpression selectCallExpression = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { source.ElementType, prop.PropertyType },
source.Expression,
lambda);
return source.Provider.CreateQuery<TResult>(selectCallExpression);
}
That way, you can use it like:
var query = _appDbContext.Persons
.Where(p => p.Id == 123)
.Select<Person,string>(wantedData)
.ToList();
I'm trying to write a dynamic query but having trouble when dealing with dynamic types in the OrderBy section of my query. I've seen some solutions using method call expressions, which may work for me, but I don't know how to fit that into my existing code where I am using method chaining on either side. It's possible I can just use dynamic as my type but I don't think that will work with Nullable types.
public async Task<IEnumerable<MyEntityModel>> GetEntities(QueryEntityResource request)
{
IQueryable<MyEntityModel> queryableData = ...;
Expression whereLambdaExp = ...;
queryableData = queryableData.Where(whereLambdaExp);
ParameterExpression param = Expression.Parameter(typeof(MyEntityModel));
PropertyInfo property = typeof(MyEntityModel).GetProperty(request.sortModel.ColId);
Expression propExpr = Expression.Property(param, property);
var lambdaExpression = Expression.Lambda<Func<MyEntityModel, dynamic>>(propExpr , new ParameterExpression[] { param }); // can't use dynamic
// Errors here; can't use dynamic type in lambda expression -- Need to specify types
if (request.sortModel.Sort == "asc")
{
queryableData = queryableData.OrderBy(lambdaExpression);
}
if (request.sortModel.Sort == "desc")
{
queryableData = queryableData.OrderByDescending(lambdaExpression);
}
queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);
return queryableData.ToListAsync();
}
If I use dynamic it won't work with my nullable types:
System.ArgumentException: Expression of type 'System.Nullable`1[System.Int32]' cannot be used for return type 'System.Object'
This is untested; but I think it is close to what you need (I feel like I've written this code before... also, see this answer):
You can build calls to OrderBy instantiated to the types you need and then build a new IQueryable. My method removes the async as I couldn't build a quick test for that in LINQPad.
public IEnumerable<MyEntityModel> GetEntities(QueryEntityResource request) {
IQueryable<MyEntityModel> queryableData = default;
Expression<Func<MyEntityModel, bool>> whereLambdaExp = default;
queryableData = queryableData.Where(whereLambdaExp);
var param = Expression.Parameter(typeof(MyEntityModel));
var propExpr = Expression.Property(param, request.sortModel.ColId);
var lambdaExpression = Expression.Lambda(propExpr, param);
if (request.sortModel.Sort == "asc" || request.sortModel.Sort == "desc")
queryableData = queryableData.Provider.CreateQuery<MyEntityModel>(Expression.Call(typeof(Queryable), request.sortModel.Sort == "asc" ? "OrderBy" : "OrderByDescending",
new[] { typeof(MyEntityModel), propExpr.Type }, queryableData.Expression, Expression.Quote(lambdaExpression)));
queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);
return queryableData.ToList();
}
I'm building a SQL "Any" clause dynamically using the System.Linq.Expressions.Expression class
I can do it like this
Expression<Func<User, Lead, bool>> predicate = (user, lead) => user.UserRoleSubProducts.Any(x => x.SubProductID == lead.SubProductID);
But I am not able to achieve this using Expression Tree.
I have tried below
var param1 = Expression.Parameter(typeof(User), "user");
var property1 = Expression.Property(param1, "UserRoleSubProducts");
var exp1 = Expression.Lambda(property1, new[] { param1 });
var param2 = Expression.Parameter(typeof(Lead), "lead");
var property2 = Expression.Property(param2, "SubProductID");
var exp2 = Expression.Lambda(property2, new[] { param2 });
var param3 = Expression.Parameter(property1.Type.GetProperty("Item").PropertyType, "x");
var property3 = Expression.Property(param3, "SubProductID");
var exp3 = Expression.Lambda(property3, new[] { param3 });
var equality = Expression.Equal(property2, property3);
var any = typeof(Queryable).GetMethods().Where(m => m.Name == "Any").Single(m => m.GetParameters().Length == 2).MakeGenericMethod(property1.Type);
var expression = Expression.Call(null, any, property1, equality);
But getting
Expression of type
'Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]' cannot be used for parameter of type System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]]' of method 'Boolean Any[DataServiceCollection1](System.Linq.IQueryable1[Microsoft.OData.Client.DataServiceCollection1[Api.Models.UserRoleSubProduct]],
System.Linq.Expressions.Expression1[System.Func2[Microsoft.OData.Client.DataServiceCollection`1[Api.Models.UserRoleSubProduct],System.Boolean]])'
I think I am close enough. Any help is appreciated
Ignoring the redundant unused lambda expressions, the problem is in the last 2 lines.
First, you are using a wrong generic type (MakeGenericMethod(property1.Type)), while the correct type is basically the type of the parameter x here
.Any(x => x.SubProductID == lead.SubProductID)
=>
.Any<T>((T x) => ...)
which maps to param3.Type in your code.
Second, the second argument of the Any must be lambda expression (not simply equality as in the code).
Third, since user.UserRoleSubProducts most likely is a collection type, you should emit call to Enumerable.Any rather than Queryable.Any.
The Expression.Call method has overload which is very handy for "calling" static generic extension methods:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
)
So the last two lines can be replaced with:
var anyCall = Expression.Call(
typeof(Enumerable), nameof(Enumerable.Any), new Type[] { param3.Type },
property1, Expression.Lambda(equality, param3)
);
I am trying to adapt the code below to build generic function which returns expression for aggregate functions such as sum, count average, min, max for list of data
Sum is working but others are not. I have Additional information: Incorrect number of arguments exception. Yes, it is clear that is Expression.Call built incorrectly for others but can not find any doc how to build the right expression for other aggregate functions.
public Expression AggregateFunc(IQueryable source, string member, string aggFunc)
{
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
FieldInfo field = source.ElementType.GetField(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "f");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, (MemberInfo)property ?? field), parameter);
// Method
var l = typeof(Queryable).GetMethods().Where(m => m.Name == aggFunc).ToList();
MethodInfo method = typeof(Queryable).GetMethods().First(m => m.Name == aggFunc );
return Expression.Call(
null,
method.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) });
}
Usage:
var list = new List<Int32FormFieldData>()
{
new FormFieldData { Path = "1", Value = 1 },
new FormFieldData { Path = "2", Value = 2 },
new FormFieldData { Path = "3", Value = 3 }
};`
AggregateFunc(list.AsQueryable(), "Value", "Count");
To make it work with Min, Max etc, you need to make some changes (see comments):
public static Expression AggregateFunc(IQueryable source, string member, string aggFunc) {
PropertyInfo property = source.ElementType.GetProperty(member);
FieldInfo field = source.ElementType.GetField(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "f");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, (MemberInfo) property ?? field), parameter);
// Method
// find correct method with two parameters: IQueryable and selector
MethodInfo method = typeof(Queryable).GetMethods().Where(c => c.GetParameters().Length == 2).First(m => m.Name == aggFunc);
// some aggregates have two generic type arguments (such as min, max, average)
// others like Sum have just one
var genArgs = new List<Type>();
genArgs.Add(source.ElementType);
if (method.GetGenericArguments().Length > 1) {
genArgs.Add(property?.PropertyType ?? field.FieldType);
}
return Expression.Call(
null,
method.MakeGenericMethod(genArgs.ToArray()),
new[] {source.Expression, Expression.Quote(selector)});
}
However, Count is different because for it, selector does not make any sense (you don't call Count(c => c.Value)), so for that it's better to create separate method with different signature (without member).
Consider this:
var propertyinfo = typeof(Customer).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
now I want to declare
Func<int,orderType>
I know its not possible directly since ordertype is at runtime but is there is any workaround ?
this is exactly what I want to do :
var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = (Expression.Lambda<Func<T, orderType>>
(Expression.Convert(Expression.Property(param, sortExpressionStr), typeof(orderType)), param));
all this because I want to convert:
Expression<Func<T,object>> to Expression<Func<T,orderType>>
or if its not possible then I want to create it from the first place with the right type , the case is as following:
I'm inside a method which have a type(Customer) and a property name of that type I want to order by it , I want to create a sort expression tree to pass it to Orderby (here).
You can do this by using an open generic type definition, and then making the specific type from that:
typeof(Func<,>).MakeGenericType(typeof(int), orderType);
However, what you're trying to do (calling Lambda<TDelegate>) is not directly possible. You must call Lambda without a type parameter:
var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = Expression.Lambda(
Expression.Convert(Expression.Property(param, sortExpressionStr),
orderType),
param));
This will create the proper Func<,> for you behind the scenes. If you want to compile the expression and use the delegate, you can only do this dynamically with
sortExpression.Compile().DynamicInvoke(param);
If you want to call the OrderBy extension method on Queryable, things get a little more complicated:
var propertyInfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyInfo.PropertyType;
// first find the OrderBy method with no types specified
MethodInfo method = typeof(Queryable).GetMethods()
.Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
.Single();
// then make the right version by supplying the right types
MethodInfo concreteMethod = method.MakeGenericMethod(typeof(T), orderType);
var param = Expression.Parameter(typeof(T), "x");
// the key selector for the OrderBy method
Expression orderBy =
Expression.Lambda(
Expression.Property(orderParam, propertyInfo),
orderParam);
// how to use:
var sequence = new T[0].AsQueryable(); // sample IQueryable
// because no types are known in advance, we need to call Invoke
// through relection here
IQueryable result = (IQueryable) concreteMethod.Invoke(
null, // = static
new object[] { sequence, orderBy });
You can use the Type.MakeGenericType Method:
Type result = typeof(Func<,>).MakeGenericType(typeof(int), orderType);
This should work:
public static IQueryable<T> OrderByField<T>(
IQueryable<T> q, string sortfield, bool ascending)
{
var p = Expression.Parameter(typeof(T), "p");
var x = Expression.Lambda(Expression.Property(p, sortfield), p);
return q.Provider.CreateQuery<T>(
Expression.Call(typeof(Queryable),
ascending ? "OrderBy" : "OrderByDescending",
new Type[] { q.ElementType, x.Body.Type },
q.Expression,
x));
}
From here.
linqClass.OrderBy(GetSortExpression(sortstr));
public static Expression<Func<T,object>> GetSortExpression<T>(string sortExpressionStr)
{
var param = Expression.Parameter(typeof(T), "x");
var sortExpression = Expression.Lambda<Func<T, object>>(Expression.Property(param, sortExpressionStr), param);
return sortExpression;
}
this worked my problem was i used to pass extra parameter Typeof(Object) and orderby used to tell me it cant sort by Object type. thanks all
thanks dtb i will check if your answer work too and i will accept it if it works if not i will accept thsi one.
You want to use Dynamic Linq, a part of the Visual Studio sample code.
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
See if my solution dynamic enough.
public class Product
{
public long ID { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
}
static void Main(string[] args)
{
List<Product> products = (from i in Enumerable.Range(1, 10)
select new Product { ID = i, Name = "product " + i, Date = DateTime.Now.AddDays(-i) }).ToList(); //the test case
const string SortBy = "Date"; // to test you can change to "ID"/"Name"
Type sortType = typeof(Product).GetProperty(SortBy).PropertyType; // DateTime
ParameterExpression sortParamExp = Expression.Parameter(typeof(Product), "p"); // {p}
Expression sortBodyExp = Expression.PropertyOrField(sortParamExp, SortBy); // {p.DateTime}
LambdaExpression sortExp = Expression.Lambda(sortBodyExp, sortParamExp); // {p=>p.DateTime}
var OrderByMethod = typeof(Enumerable).GetMethods().Where(m => m.Name.Equals("OrderBy") && m.GetParameters().Count() == 2).FirstOrDefault().MakeGenericMethod(typeof(Product), sortType);
var result = OrderByMethod.Invoke(products, new object[] { products, sortExp.Compile() });
}
Base on above, it's not difficult to change Product to T to make it generic.
You can get the Type associated with Func<int,orderType> in case you wanted to e.g. pass it into CreateDelegate.
But what are you ultimately wanting to do with it? There may be a more straightforward approach.