I want to populate the itemsource of a ComboBox with items from my List, depending on which property from T is selected.
The statement should be like:
foreach property which is a string,
select the values of the property, make distinct.
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
var queryableData = data.AsQueryable();
var paramExp = Expression.Parameter(typeof(T), "left");
foreach (var pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
var callExpr = Expression.MakeMemberAccess(paramExp, pi);
var lambdaExpr = Expression.Lambda(callExpr) ;
// From here on it goes wrong!!!
var comleteExpr = lambdaExpr as Expression<Func<T, bool>>;
var compiledExpr = comleteExpr.Compile();
var res = data.Select(compiledExpr).Distinct().ToList();
// add to results ...
}
return null;
}
The problem seems to be the casting from the lambda expression to prepare for compilation.
Thank you for your help.
First of all you need to provide paramExp to lambda. Secondly there is generic version of Lamda method which is just easier to use. Finally, you don't need to compile expression when you use IQueryable. You created queryableData variable and didn't use it.
Here is code:
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
var queryableData = data.AsQueryable();
var paramExp = Expression.Parameter(typeof(T), "left");
foreach (var pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
var callExpr = Expression.MakeMemberAccess(paramExp, pi);
var lambdaExpr = Expression.Lambda<Func<T, bool>>(callExpr, paramExp);
var res = queryableData.Select(lambdaExpr).Distinct().ToList();
// add to results ...
}
return null;
}
I think you should check if the casting result is not null :
public Dictionary<string, List<string>> CreateSuggestionsLists<T>(List<T> data)
{
IQueryable<T> queryableData = data.AsQueryable();
ParameterExpression paramExp = Expression.Parameter(typeof(T), "left");
foreach (PropertyInfo pi in typeof(T).GetProperties().Where(p => p.PropertyType == typeof(string)))
{
MemberExpression callExpr = Expression.MakeMemberAccess(paramExp, pi);
LambdaExpression lambdaExpr = Expression.Lambda(callExpr);
// From here on it goes wrong!!!
if (!(lambdaExpr is Expression<Func<T, bool>> comleteExpr)) continue;
Func<T, bool> compiledExpr = comleteExpr.Compile();
List<bool> res = data.Select(compiledExpr).Distinct().ToList();
// add to results ...
}
return null;
}
Related
I'm using Visual Studio 2019. I'm doing filter with the below function. My search result is always sensitive for a string value. For employee name, it would be case-insensitive search, so user can enter upper or lowercase searchValue. My list is converted ToList() to AsQueryable()
list = list.Where(AppendFilters<CustomerApiResponse>(data.filter.filters));
Please see the below function:
protected ExpressionStarter<T> AppendFilters<T>(List<Filter2> filters)
{
var predicate = PredicateBuilder.New<T>();
foreach (var item in filters)
{
predicate = predicate.And(GetExpression<T>(item.field, item.value, item.#operator));
}
return predicate;
}
Here's what I have:
private Expression<Func<T, bool>> GetExpression<T>(string propName, object val, string op)
{
ParameterExpression argParam = Expression.Parameter(typeof(T));
var nameProperty = Expression.Property(argParam, propName);
var opp = EnumExtensions.ParseEnum<KendoOperators>(op).GetDescription();
MethodInfo method = typeof(string).GetMethod(opp, new[] { typeof(string) });
Expression e1;
if (nameProperty.Type == typeof(int))
{
var propertyType = ((PropertyInfo)nameProperty.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);
if (!converter.CanConvertFrom(typeof(string)))
throw new NotSupportedException();
var propertyValue = converter.ConvertFrom(val);
var constant = Expression.Constant(propertyValue);
var valueExpression = Expression.Convert(constant, propertyType);
if (op == "neq")
{
e1 = Expression.NotEqual(nameProperty, valueExpression);
}
else
{
e1 = Expression.Equal(nameProperty, valueExpression);
}
}
else
{
var constant = Expression.Constant(val);
e1 = Expression.Call(nameProperty, method, constant);
}
return Expression.Lambda<Func<T, bool>>(e1, argParam);
}
How do I change the above code to make filter to be case-insensitive for searches any string value?
Thank you!
I would like to change the following code so as to handle grouping of more than 1 property
private Expression<Func<ProfileResultView, string>> DynamicGroupBy(string propertyName)
{
var parameterExp = Expression.Parameter(typeof(ProfileResultView), "x");
var memberExp = Expression.PropertyOrField(parameterExp, propertyName);
return Expression.Lambda<Func<ProfileResultView, string>>(memberExp, parameterExp);
}
so then this would be translated to
GroupBy(x => new { x.Column1, x.Column2 })
how can I write the anonymous type in the expression-tree syntax?
If the type of the grouping key does not matter for you, you can create types dynamically and call the grouping based on those types:
public static Expression<Func<TSource, object>> DynamicGroupBy<TSource>
(params string[] properties)
{
var entityType = typeof(TSource);
var props = properties.Select(x => entityType.GetProperty(x)).ToList();
var source = Expression.Parameter(entityType, "x");
// create x=> new myType{ prop1 = x.prop1,...}
var newType = CreateNewType(props);
var binding = props.Select(p => Expression.Bind(newType.GetField(p.Name),
Expression.Property(source, p.Name))).ToList();
var body = Expression.MemberInit(Expression.New(newType), binding);
var selector = Expression.Lambda<Func<TSource, object>>(body, source);
return selector;
}
public static Type CreateNewType(List<PropertyInfo> props)
{
AssemblyName asmName = new AssemblyName("MyAsm");
AssemblyBuilder dynamicAssembly = AssemblyBuilder
.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("MyAsm");
TypeBuilder dynamicAnonymousType = dynamicModule
.DefineType("MyType", TypeAttributes.Public);
foreach (var p in props)
{
dynamicAnonymousType.DefineField(p.Name, p.PropertyType, FieldAttributes.Public);
}
return dynamicAnonymousType.CreateType();
}
Note that the group key type is object.
Im trying to implement Dynamic LINQ Query for selected columns to show has Output columns in LINQ Query.
Here is the error:
/Property 'System.String CompanyCode' is not defined for type
'System.String'"
public static void SelectProjection()
{
DataMovementDataContext dbMovement = new DataMovementDataContext();
var entity = dbMovement.ListofAccountingDocs2_1075s.AsQueryable();
Type type = entity.ElementType;
var entityParam = Expression.Parameter(entity.ElementType, "row");
Expression expr = entityParam;
string[] props = "AccountingDocumentNbr,CompanyCode,FiscalYearNbr".Split(',');
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
// row => row.Property
// var columnLambda = Expression.Lambda( Expression.Property(entityParam, "GLCompanyCode"), entityParam);
var columnLambda = Expression.Lambda(Expression.Property(expr, "AccountingDocumentNbr,GLCompanyCode"), entityParam);
// Items.Select(row => row.Property)
var selectCall = Expression.Call(typeof(Queryable), "Select", new Type[] { entity.ElementType, columnLambda.Body.Type }, entity.Expression, columnLambda);
// Items.Select(row => row.Property).Distinct
var distinctCall = Expression.Call(typeof(Queryable), "Distinct", new Type[] { typeof(string) }, selectCall);
// colvalue => colvalue
var sortParam = Expression.Parameter(typeof(string), "AccountingDocumentNbr");
var columnResultLambda = Expression.Lambda(sortParam, sortParam);
// Items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)
var ordercall = Expression.Call(typeof(Queryable), "OrderBy",
new Type[] { typeof(string), columnResultLambda.Body.Type },
distinctCall, columnResultLambda);
var result = entity.Provider.CreateQuery(ordercall);
foreach (var item in result)
{
Console.Write(item);
}
}
Can any one provide me help in solving above error?
If we look at this code:
// I chnaged this part:
string[] props = new string[]
{
"AccountingDocumentNbr",
"CompanyCode",
"FiscalYearNbr"
};
Expression expr;
Type type = entity.ElementType;
foreach (string prop in props)
{
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType; // This line may cause your loop
// to do something you dont want to do?
}
It seems to me that your loop going down the properties, gets a string and then searches for a prop called CompanyCode, which the class String obviously doesnt have.
Your loop does the following at the moment:
from type get the property named AccountingDocumentNbr
from the return type of property AccountingDocumentNbr get the property called CompanyCode
form the return type of property CompanyCode get the type of the property called FiscalYearNbr
I hardly doubt this is what you really want to do.
I have the following code block I am using to perform some dynamic filtering on a generic IQueryable list
private static MethodInfo miTL = typeof(String).GetMethod("ToLower", Type.EmptyTypes);
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
//If the type is string, force lowercase equals comparison for both sides.
value = value.ToString().ToLower();
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
//ToLower dynamic expression
var dynamicExpression = Expression.Call(parameterProperty, miTL);
var constantValue = Expression.Constant(value);
var equalsExpression = Expression.Equal(dynamicExpression, constantValue);
var lambdaExpression = Expression.Lambda<Func<T, bool>>(equalsExpression, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
This all works fine - apart from the possibility of my source containing null values, which then returns a null reference exception.
It directly translates to (when the field is "Counterparty" :- {item => (item.Counterparty.ToLower() == "name of counterparty")}
What I actually need in Lambda expression form is :-
{item => !string.IsNullEmptyOrWhitespace(item.Counterparty) && (item.Counterparty.ToLower() == "name of counterparty")}
Any ideas how to achieve this dynamically?
--REVIEWED--
Here is the whole replacement code, using a much nicer string.Equals check
public static IQueryable<T> Where<T>(IQueryable<T> source, string member, object value)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (member == null)
{
throw new ArgumentNullException("member");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (value is string && !string.IsNullOrWhiteSpace(value.ToString()))
{
var parameter = Expression.Parameter(typeof(T), "item");
var parameterProperty = Expression.Property(parameter, member);
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrEmpty", null, parameterProperty)
),
Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase))
);
var body2 = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant(value),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));
var lambdaExpression = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(lambdaExpression);
}
else
{
var item = Expression.Parameter(typeof(T), "item");
var prop = Expression.Property(item, member);
var soap = Expression.Constant(value);
var equal = Expression.Equal(prop, soap);
var lambda = Expression.Lambda<Func<T, bool>>(equal, item);
return source.Where(lambda);
}
}
A literal translation would be something like:
var body =
Expression.AndAlso(
Expression.Not(
Expression.Call(typeof(string), "IsNullOrWhiteSpace", null,
parameterProperty)
),
Expression.Equal(
Expression.Call(parameterProperty, "ToLower", null),
Expression.Constant("name of counterparty")
)
);
However, you would do well to look at the various string.Equals overloads. For example:
var body = Expression.Call(typeof(string), "Equals", null,
parameterProperty, Expression.Constant("name of counterparty"),
Expression.Constant(System.StringComparison.InvariantCultureIgnoreCase));
I have this bit of code as an example, basically it spits out
p => p.fieldname.StartsWith("123")
But who would i expand on this to do something like this:
p => p.anotherentity.fieldname.StartsWith("123")
Here is a sample of the code i am have refactored for own needs:
string propertyName = "FirstName";
string methodName = "StartsWith";
string keyword = "123";
Type t = typeof (Person);
ParameterExpression paramExp = Expression.Parameter(t, "p");
// the parameter: p
MemberExpression memberExp = Expression.MakeMemberAccess(paramExp,
t.GetMember(propertyName).FirstOrDefault());
// part of the body: p.FirstName
MethodCallExpression callExp = Expression.Call(memberExp,
typeof (string).GetMethod(methodName,
new Type[] {typeof (string)}),
Expression.Constant(keyword));
// the body: p.FirstName.StartsWith("123")
Expression<Func<Person, bool>> whereExp = Expression.Lambda<Func<Person, bool>>(callExp, paramExp);
Expression<Func<Person, string>> selectExp = Expression.Lambda<Func<Person, string>>(memberExp, paramExp);
Console.WriteLine(whereExp); // p => p.FirstName.StartsWith("123")
Console.WriteLine(selectExp); // p => p.FirstName
To further explain let me show you what i would like to do:
public class Person
{
public string IdentityCode {get;set;}
public Loans Loans {get;set;}
}
public class Loans
{
public int Id {get;set;}
public Asset Assets {get;set;}
public Person person {get;set;}
}
public class Asset
{
public string SerialNumber {get;set;}
}
Then using an expression build something like this:
p => p.Loans.Asset.SerialNumber.StartsWith("123)
Or
p => p.Loans.Person.IdentityCode.StartsWith("123")
untested, but...
ParameterExpression paramExp = Expression.Parameter(t, "p"); // the parameter: p
MemberExpression memberExp =
Expression.MakeMemberAccess(paramExp, t.GetMember(propertyName).FirstOrDefault());
would become something like:
ParameterExpression paramExp = Expression.Parameter(t, "p"); // the parameter: p
MemberExpression otherEntityExp =
Expression.MakeMemberAccess(paramExp, t.GetMember("anotherentity").FirstOrDefault());
MemberExpression memberExp =
Expression.MakeMemberAccess(otherEntityExp, t.GetMember(propertyName).FirstOrDefault());
I'm not sure what you're asking for, updating an expression or building one from scratch...
If you already have the existing, old expression and want to update it, it would be very easy to create a new one. The idea is to dig through the expression tree down to the expression you want to replace. Then update all parent expressions with the newly replaced one.
Expression<Func<Obj, bool>> expr = p => p.fieldname.StartsWith("123");
var body = expr.Body as MethodCallExpression; // *.StartsWith()
var obj = body.Object as MemberExpression; // p.fieldname
var param = expr.Parameters.First(); // p
var newAccess = Expression.PropertyOrField(param, "anotherentity"); // p.anotherentity
var newObj = obj.Update(newAccess); // update obj
var newBody = body.Update(newObj, body.Arguments); // update body
var newExpr = expr.Update(newBody, expr.Parameters);// update expr
Otherwise to build up the expression tree:
Expression<Func<Person, bool>> expr =
p => p.Loans.Asset.SerialNumber.StartsWith("123");
Work it out from the beginning.
var p = Expression.Parameter(typeof(Person), "p");
var accessLoans = Expression.PropertyOrField(p, "Loans");
var accessAsset = Expression.PropertyOrField(accessLoans, "Asset");
var accessSerialNumber = Expression.PropertyOrField(accessAsset, "SerialNumber");
var callArgs = new Expression[] { Expression.Constant("123", typeof(string)) };
var callStartsWith = Expression.Call(accessSerialNumber, "StartsWith", null, callArgs);
var newExpr = Expression.Lambda<Func<Person, bool>>(callStartsWith, p);
I'll leave the last one as an exercise for you.