Generate lambda Expression By Clause using string.format in C#? - c#

I have a method to Generate Expression By Clause as below:
internal static Expression<Func<TModel, T>> GenExpressionByClause<TModel, T>(string column)
{
PropertyInfo columnPropInfo = typeof(TModel).GetProperty(column);
var entityParam = Expression.Parameter(typeof(TModel), "e"); // {e}
var columnExpr = Expression.MakeMemberAccess(entityParam, columnPropInfo); // {e.column}
var lambda = Expression.Lambda(columnExpr, entityParam) as Expression<Func<TModel, T>>; // {e => e.column}
return lambda;
}
So I could build lambda expression as e=>e.column.
but I want to change it to e=>string.Format("{0}",e.column). How could I refactor the code?

This should do the work:
internal static Expression<Func<TModel, T>> GenExpressionByClause<TModel, T>(string column)
{
var columnPropInfo = typeof(TModel).GetProperty(column);
var formatMethod = typeof (string).GetMethod("Format", new[] {typeof (string), typeof (object)});
var entityParam = Expression.Parameter(typeof(TModel), "e");
var columnExpr = Expression.MakeMemberAccess(entityParam, columnPropInfo);
var formatCall = Expression.Call( formatMethod, Expression.Constant("{0}"), columnExpr);
var lambda = Expression.Lambda(formatCall , entityParam) as Expression<Func<TModel, T>>;
return lambda;
}
Note that you could cache the Format MethodInfo in a static field.

Final solution:
internal static Expression<Func<TModel, string>> GenExpressionByClauseEx<TModel>(string column)
{
var columnPropInfo = typeof(TModel).GetProperty(column);
var formatMethod = typeof(String).GetMethod("Format", new[] { typeof(string), typeof(Object) });
//string.Format(
var entityParam = Expression.Parameter(typeof(TModel), "e");
var columnExpr = Expression.MakeMemberAccess(entityParam, columnPropInfo);
var columnExprObj=Expression.Convert(columnExpr, typeof(object));
var formatCall = Expression.Call(formatMethod, Expression.Constant("111--{0}"), columnExprObj);
var lambda = Expression.Lambda(formatCall, entityParam) as Expression<Func<TModel, string>>;
return lambda;
}

Related

C# Select Expr off a GroupBy that returns a Dictionary

Using Net 4.5.1, C#....
With IQueryable I have a GroupBy clause that returns a Dictionary. This is done by the following code:
public static Expression<Func<ChartJoin, Dictionary<string, object>>> GetGroupByDictionary(NameValueCollection fields)
{
var parameter = Expression.Parameter(typeof(ChartJoin));
var addMethod = typeof(Dictionary<string, object>)
.GetMethod(
"Add",
new[] { typeof(string), typeof(object) }
);
var selector = Expression.ListInit(
Expression.New(typeof(Dictionary<string, object>)),
fields.AllKeys.Select(
key => Expression.ElementInit(
addMethod,
Expression.Constant(key),
Expression.Convert(
Chart.getNestedPropertyOrField(parameter, fields[key]), // basically drills down to a nested property (note: static method not shown to save space)
typeof(object)
)
)
)
);
var lambda = Expression.Lambda<Func<ChartJoin, Dictionary<string, object>>>(selector, parameter);
return lambda;
}
The call is then:
NameValueCollection fields = new NameValueCollection();
fields.Add("Year", "Respondent.currentVisitYear");
fields.Add("Month", "Respondent.currentVisitMonth");
// .... could be more fields
<some IQueryable<ChartJoin>
.GroupBy(
Chart.GetGroupByDictionary(fields).Compile(),
new DictionaryComparer<string, object>()
);
The DictionaryComparer allows for uniqueness providing an Equals and GetHashCode implementation. I want to return a Dictionary with the Select clause. Trying just a simple example of selecting one of the GroupBy keys (for example Select(GetKey("Year").Compile())):
private static Expression<Func<IGrouping<IDictionary<string, object>, ChartJoin>, Dictionary<string, object>>> GetKey(String key)
{
var block = ? /// Need logic to get a the IGrouping.Key property and pull the value
var addMethod = typeof(Dictionary<string, object>)
.GetMethod(
"Add",
new[] { typeof(string), typeof(object) }
);
var selector = Expression.ListInit(
Expression.New(typeof(Dictionary<string, object>)),
Expression.ElementInit(
addMethod,
Expression.Constant(key),
Expression.Convert(
block,
typeof(object)
)
)
);
var lambda = Expression.Lambda<Func<IGrouping<IDictionary<string, object>, ChartJoin>, Dictionary<string, object>>>(selector, parameter);
return lambda;
}
Would be great if somebody could get me started with the above (i.e. how to create the block expression to pull a Dictionary value from a GroupBy.Key).
Answering my own question since comments is too shallow a space for code.
Basically, writing expressions for Select wasn't rocket science after a bit of experimentation. For example, here is the code for pulling a group Key into the selection:
private static Expression<Func<IGrouping<IDictionary<string, object>, Respondent>, IDictionary<string, object>>> GetKey(String field)
{
// x =>
var ParameterType = typeof(IGrouping<IDictionary<string, object>, Respondent>);
var parameter = Expression.Parameter(ParameterType, "x");
// x => x.Key
var Key = Expression.Property(parameter, "Key");
// x => x.Key[field]
Expression KeyExpression = Expression.Property(Key, "Item", new Expression[] { Expression.Constant(field) });
ParameterExpression KeyResult = Expression.Parameter(typeof(object));
BlockExpression block = Expression.Block(
new[] { KeyResult },
Expression.Assign(KeyResult, KeyExpression),
KeyResult
);
.... // <- the block is put in the selector (as shown above)
}
Doing a Count on the GroupBy was also straightforward when I got my head around static method calls:
// x => x.Count()
MethodInfo CountMethod = (typeof(Enumerable))
.GetMethods()
.First(
method => method.Name == "Count"
&& method.IsGenericMethod
)
.MakeGenericMethod(typeof(Respondent));
Expression CountExpression = Expression.Call(null, CountMethod, parameter);
Sum was also fun:
// x => x.Sum(m => m.MWEIGHT) where MWEIGHT is a ?decimal (i.e. nullable)
var m = Expression.Parameter(typeof(Respondent), "m");
PropertyInfo SumPropertyInfo = typeof(Respondent).GetProperty("MWEIGHT");
Expression SumSelector = Expression.Lambda(
Expression.Convert(Expression.MakeMemberAccess(m, SumPropertyInfo), typeof(decimal)),
m
);
MethodInfo SumMethod = (typeof(Enumerable))
.GetMethods()
.First(
method => method.Name == "Sum"
&& method.ReturnType == typeof(decimal)
&& method.IsGenericMethod
)
.MakeGenericMethod(typeof(Respondent));
Expression SumExpression = Expression.Call(null, SumMethod, parameter, SumSelector);

LINQ Expression Tree [duplicate]

This question already has answers here:
LINQ Expression Tree Any() inside Where()
(2 answers)
Closed 9 years ago.
I want to generate expression trees using the API for the following:
var managers = dataContext.Employees.Where(e => e.Subordinates.Any());
Additionally, how do I then generate the expression tree to do this:
var managedEmployees = managers.ToDictionary(key => key.Manager, value => value.Subordinates.Select(s => s.FullName));
I've come up with the following so far for the .Where(), but it errors because it doesn't like the type parameters in new Type[] { typeof(Func<Employee, IEnumerable<Employee>>) }.
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
MethodCallExpression hasSubordinates = Expression.Call(typeof(Enumerable),
"Any",
new Type[] { typeof(Employee) },
subordinatesProperty);
LambdaExpression whereLambda = Expression.Lambda(hasSubordinates, employeesParameter);
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { typeof(Func<Employee, IEnumerable<Employee>>) },
dataContext.Employees.AsQueryable(),
whereLambda);
I got this. The type parameters on Any and Where need to be Employee, not IQueryable<Employee> or IEnumerable<Employee> because it's just looking for the type parameters, not the actual types. I believe you also need an Expression.Constant(dataContext.Employees) instead of straight dataContext.Employees.
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
MethodCallExpression hasSubordinates = Expression.Call(typeof(Enumerable),
"Any",
new Type[] { typeof(Employee) },
subordinatesProperty);
LambdaExpression whereLambda = Expression.Lambda(hasSubordinates, employeesParameter);
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { typeof(Employee) },
Expression.Constant(dataContext.Employees),
whereLambda);
To call Any for your MemberExpression you should do this
ParameterExpression employeesParameter = Expression.Parameter(typeof(Employee), "e");
MemberExpression subordinatesProperty = Expression.Property(employeesParameter, typeof(Employee).GetProperty("Subordinates"));
var mi = typeof(Enumerable).GetMethods().First(x => x.Name == "Any" && x.GetParameters().Length == 1);
mi = mi.MakeGenericMethod(typeof (bool));
var hasSubordinates = Expression.Call(mi, subordinatesProperty);
and Where
var lambda = Expression.Lambda<Func<Employee, bool>>(hasSubordinates, employeesParameter);
var res = i.Where(lambda.Compile());

Dynamically construct "or" LIKE query in LINQ to SQL

I have a LINQ query which is composed by an anonymous object.
At a given point, I want to limit the results by incoming search parameters, but this can be one or more parameters, and I want to perform a "LIKE x OR LIKE y OR LIKE z" using those.
In code, it would look like this:
reservations = reservations.Where(r =>
r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) ||
r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) ||
// Parameter 3, 4, 5,..
);
How could I construct this dynamically, knowing that reservations is of the type IQueryable<'a> (anonymous object)? I've looked around on various resources and I can only seem to find a way to do it when I know the type, not when using anonymous types.
It's important to know that it's Linq to SQL, so it should be translated to an SQL query and not be filtered in memory...
There are two possible ways:
Building an Expression, as pointed out by Coincoin
Putting all your parameters into an array and using Any:
var parameters = new [] { parameter1, parameter2, /*...*/ }
reservations = reservations
.Where(r =>
parameters.Any(p => r.GuestFirstName.Contains(p)
|| r.GuestLastName.Contains(p)));
I would write my own generic extension method:
public static class CollectionHelper
{
public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values)
{
var lambda = CombineLambdas<T>(properties, values);
var result = typeof (Queryable).GetMethods().First(
method => method.Name == "Where"
&& method.IsGenericMethodDefinition)
.MakeGenericMethod(typeof (T))
.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) result;
}
// combine lambda expressions using OR operator
private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values)
{
var param = Expression.Parameter(typeof (T));
LambdaExpression prev = null;
foreach (var value in values)
{
foreach (var property in properties)
{
LambdaExpression current = GetContainsExpression<T>(property, value);
if (prev != null)
{
Expression body = Expression.Or(Expression.Invoke(prev, param),
Expression.Invoke(current, param));
prev = Expression.Lambda(body, param);
}
prev = prev ?? current;
}
}
return prev;
}
// construct expression tree to represent String.Contains
private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof (T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var someValue = Expression.Constant(propertyValue, typeof (string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
}
and the usage:
var reservations = new List<TheType>() // sample collection
{
new TheType {FirstName = "aa", LastName = "bb"},
new TheType {FirstName = "cc", LastName = "dd"},
new TheType {FirstName = "ee", LastName = "ff"}
}.AsQueryable();
var filtered = reservations
.Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"});
/* returnes 2 elements:
* {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */
I don't know a general solution you'd like to have - if exists any, but I hope it can be acceptable alternative which solves your case by building desired filter dynamically.
I found the solution after some debugging, but I create a WhereFilter with multiple selectors, one for FirstName and one for LastName..
This is the extension method:
public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors)
{
List<Expression> expressions = new List<Expression>();
var param = Expression.Parameter(typeof(T), "p");
var bodies = new List<MemberExpression>();
foreach (var s in selectors)
{
bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name));
}
foreach (var v in possibleValues)
{
foreach(var b in bodies) {
expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v)));
}
}
var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal));
return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param));
}
It can be used like this:
reservations = reservations.WhereFilter(
array_of_allowed_values,
r => r.GuestFirstName,
r => r.GuestLastName
);
I checked the trace string of the query and it actually translated to SQL, so the filtering is performed at the database.

How to create an ExpressionTree with multiple method calls

I currently have the following code which allows me to call any method required on the EmailAddress property of my object, and it works great:
public static Expression<Func<T, bool>> BuildEmailAddressLambda(string method, params object[] args) {
var e = Expression.Parameter(typeof(T), "e");
var propertyInfo = typeof(T).GetProperty("EmailAddress");
var m = Expression.MakeMemberAccess(e, propertyInfo);
var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
var c = args.Select(a => Expression.Constant(a, a.GetType())).ToArray();
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(Expression.Call(m, mi, c), e);
return lambda;
}
// called:
lambda = LambdaExpressionHelper<MailingListMember>.BuildEmailAddressLambda("StartsWith", "r", StringComparison.OrdinalIgnoreCase);
However, I now need to amend the code so that it works for members of my object other than EmailAddress. Specifically, I would like to create a tree to cover the following expression, where multiple method calls are used:
e.GetStringValue(12).StartsWith("r", StringComparison.OrdinalIgnoreCase);
I have made several attempts, all of which end up in various errors. I feel I am missing something in the logic of how ExpressionTrees are created and some help with this would be greatly appreciated.
Thanks
Is this good for you?
public static Expression<Func<T, bool>> BuildEmailAddressLambda<T>(
string member, IEnumerable<object> memberArgs, string method, params object[] args)
{
var e = Expression.Parameter(typeof(T), "e");
var memberInfo =
(MemberInfo) typeof(T).GetField(member) ??
(MemberInfo) typeof(T).GetProperty(member) ??
(MemberInfo) typeof(T).GetMethod(member, (memberArgs ?? Enumerable.Empty<object>()).Select(p => p.GetType()).ToArray());
Expression m;
if (memberInfo.MemberType == MemberTypes.Method)
{
var a = memberArgs.Select(p => Expression.Constant(p));
m = Expression.Call(e, (MethodInfo) memberInfo, a);
}
else
{
m = Expression.MakeMemberAccess(e, memberInfo);
}
var mi = m.Type.GetMethod(method, args.Select(a => a.GetType()).ToArray());
var c = args.Select(a => Expression.Constant(a, a.GetType()));
return Expression.Lambda<Func<T, bool>>(Expression.Call(m, mi, c), e);
}
// called:
lambda = LambdaExpressionHelper<MailingListMember>.BuildEmailAddressLambda("EmailAddress", null, "StartsWith", "r", StringComparison.OrdinalIgnoreCase);
// or
lambda = LambdaExpressionHelper<MailingListMember>.BuildEmailAddressLambda("GetStringValue", new object[] { 12 }, "StartsWith", "r", StringComparison.OrdinalIgnoreCase);

How to create Expression

I have 3 variables:
String propertyName = "Title";
String propertyValue = "Bob";
Type propertyType = typeof(String);
How can I construct Expression <Func<T, bool>>,
if T object has property Title?
I need Expression:
item => item.Title.Contains("Bob")
if propertyType is bool, then I need
item => item.OtherOproperty == false/true
and so on...
This code performs filtering and stores results in filtered array:
IQueryable<T> queryableData = (Items as IList<T>).AsQueryable<T>();
PropertyInfo propInfo = typeof(T).GetProperty("Title");
ParameterExpression pe = Expression.Parameter(typeof(T), "Title");
Expression left = Expression.Property(pe, propInfo);
Expression right = Expression.Constant("Bob", propInfo.PropertyType);
Expression predicateBody = Expression.Equal(left, right);
// Create an expression tree that represents the expression
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<T, bool>>(predicateBody, new ParameterExpression[] { pe }));
T[] filtered = queryableData.Provider.CreateQuery<T>(whereCallExpression).Cast<T>().ToArray();

Categories