I am to build a dynamic Expression Tree for GroupBy. All i want to achieve is like this.
var NestedGrouped = listOfPerson.GroupByMany(x => x.Name,x=>x.Age).ToList();
My Person Class is like :-
class Person
{
public string Name{ get; set; }
public int Age{ get; set; }
public float Salary{ get; set; }
}
public class GroupResult
{
public object Key { get; set; }
public int Count { get; set; }
public IEnumerable Items { get; set; }
public IEnumerable<GroupResult> SubGroups { get; set; }
public override string ToString()
{ return string.Format("{0} ({1})", Key, Count); }
}
public static class MyEnumerableExtensions
{
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
return
elements.GroupBy(selector).Select(
g => new GroupResult
{
Key = g.Key,
Count = g.Count(),
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
}
else
return null;
}
}
For Single Property I am able to build the expression but i want to do GROUPBY
with multiple columns as shown above.
FOR SINGLE PROPERTY :-
ParameterExpression parameter = Expression.Parameter(typeof(Person), "lambdaKey");
var menuProperty = Expression.PropertyOrField(parameter, "Name");
var lambda = Expression.Lambda<Func<Person, string>>(menuProperty, parameter);
var selector = lambda.Compile();
var result = P1.GroupByMany(selector);// P1 is list of PERSON
How to ADD multiple columns in Expression Tree (e.g (x => x.Name,x=>x.Age)).
Please Help. Thanks in Advance.
GroupByMany() accepts array of delegates, one delegate for each key. So, what you need is to create and compile a separate expression for each key.
The code could look something like:
private static Func<TElement, object> CreateSelector<TElement>(string key)
{
var parameter = Expression.Parameter(typeof(TElement), "lambdaKey");
var property = Expression.PropertyOrField(parameter, key);
var lambda = Expression.Lambda<Func<TElement, string>>(property, parameter);
return lambda.Compile();
}
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params string[] groupKeys)
{
return elements.GroupByMany(groupKeys.Select(CreateSelector<TElement>).ToArray());
}
Related
I really don't like to hard code the name of properties of my models. So I came up with this code so far. My code is working fine and it does exactly what I want but in an ugly way. I'm pretty sure it will be problematic soon. So any help to improve it and make it work in the right way is appreciated. I'm looking to fine better way to extract selected property names without converting expression body to string. Any change to any part of this class is fine with me. Even changing usage as long as I don't hard code my property names.
What is the better way to extract selected properties name of a model?
Here is my code:
public class Selector<T> : IDisposable
{
Dictionary<string, Func<T, object>> Selectors = new Dictionary<string, Func<T, object>>();
public Selector(params Expression<Func<T, object>>[] Selector)
{
foreach (var select in Selector)
{
//string MemberName = CleanNamesUp(select.Body.ToString());
//Func<T, object> NewSelector = select.Compile();
#region Ugly Part 1
Selectors.Add(CleanNamesUp(select.Body.ToString()), select.Compile());
#endregion
}
}
#region I am Doing This So I can Use Using(var sl = new Selector<T>())
public void Dispose()
{
Selectors.Clear();
Selectors = null;
}
#endregion
#region Ugly Part 2
private string CleanNamesUp(string nameStr)
{
string name = nameStr.Split('.')[1];
if (name.Contains(","))
{
name = name.Split(',')[0];
}
return name;
}
#endregion
public Dictionary<string, object> GetFields(T Item)
{
Dictionary<string,object> SetFieldList = new Dictionary<string, object>();
foreach(var select in Selectors)
{
SetFieldList.Add( select.Key , select.Value(Item));
}
return SetFieldList;
}
public List<Dictionary<string, object>> GetFields(IEnumerable<T> Items)
{
List<Dictionary<string, object>> SetFieldListMain = new List<Dictionary<string, object>>();
foreach (var item in Items)
{
Dictionary<string, object> SetFieldList = new Dictionary<string, object>();
foreach (var select in Selectors)
{
SetFieldList.Add(select.Key, select.Value(item));
}
SetFieldListMain.Add( SetFieldList);
}
return SetFieldListMain;
}
internal List<string> GetKeys()
{
return new List<string>(this.Selectors.Keys);
}
}
This is my model:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool IsEnabled { get; set; }
public bool IsLocked { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime LockedAt { get; set; }
}
And I am using it like this:
User user1 = new User();
user1.Email = "testDev#gmail.com";
user1.UserName = "dora";
user1.Password = "123456";
var UpObject = new Selector<User>( x => x.UserName, x => x.Password, x => x.Email, x => x.IsEnabled );
Dictionary<string,object> result = UpObject.GetFields(user1);
You can avoid parsing the expressions as string if you instead parse them as System.Linq.Expressions.
Full code sample follows, but not exactly for your code, I used DateTime instead of the generic T, adapting should just be find&replace:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ExprTest
{
class Program
{
static void Main(string[] args)
{
#region Usage
Expression<Func<DateTime, object>> propertySelector = x => x.Day;
Expression<Func<DateTime, object>> methodSelector = x => x.AddDays(1.5);
Expression[] inputSelectors = new Expression[] { propertySelector, methodSelector };
#endregion
//These are your final Selectors
Dictionary<string, Func<DateTime, object>> outputSelectors = new Dictionary<string, Func<DateTime, object>>();
//This would be in your Selector<T> constructor, replace DateTime with T.
//Instead of CleanNamesUp you would decide which part to use by extracting the appropriate Expression argument's Name.
foreach (Expression<Func<DateTime, object>> selectorLambda in inputSelectors)
{
Expression selectorExpression = selectorLambda.Body;
string name = null;
while (string.IsNullOrEmpty(name))
{
switch (selectorExpression)
{
#region Refine expression
//Necessary for value typed arguments, which get boxed by Convert(theStruct, object)
case UnaryExpression unary:
selectorExpression = unary.Operand;
break;
//add other required expression extractions
#endregion
#region Select expression key/name
case MemberExpression fieldOrProperty:
name = fieldOrProperty.Member.Name;
break;
case MethodCallExpression methodCall:
name = methodCall.Method.Name;
break;
//add other supported expressions
#endregion
}
}
outputSelectors.Add(name, selectorLambda.Compile());
}
//Set a breakpoint here to explore the outputSelectors
}
}
}
There could be a library for this, but i don't know about any, except PredicateBuilder for when you need to unify lambda arguments into one lambda expression.
I think maybe you forgot an important keyword 'nameof'. With the keyword, the code will be like this:
class User
{
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
}
static Dictionary<string, object> GetFieldsOf<T>(T item, params string[] args)
{
var properties = args.Select(property => typeof(T).GetProperty(property));
return properties.ToDictionary(property => property.Name, property => property.GetValue(item));
}
static void Main(string[] args)
{
var user = new User { Name = "Abel", Address = "Unknown", Tel = "XXX-XXX" };
var result = GetFieldsOf(user, nameof(User.Name), nameof(User.Address));
}
This code will result in some performance problems caused by reflection. But fortunately, you can avoid these by emitting a small segement of code.
//MSIL
ldarg.0
call Property.GetMethod
ret
And replace it with proerpty.GetValue. These code can be generated and cached per type, which is still worthwhile.
I want to create generic expression filtering in IQueryable
public class Vehicle
{
public int Id { get; set; }
public string VehicleNO { get; set; }
public int DriverId { get; set; }
public Driver Driver {get;set;}
}
public class Driver
{
public int Id { get; set; }
public string Name { get; set; }
}
operator = "Contain", field name = "Driver.Name", Value filter =
"Micheal"
I don't know how to filter driver name.
Here is my full code
IQueryable<SysClientSiteUser> query = entity.SysClientSiteUsers.Include(i => i.SysClientSiteRole);
Dictionary<string, string> dtFilter = new Dictionary<string, string>();
dtFilter.Add("VehicleNo", "A123");
Dictionary<Type, Func<string, object>> lookup = new Dictionary<Type, Func<string, object>>();
lookup.Add(typeof(string), x => { return x; });
lookup.Add(typeof(long), x => { return long.Parse(x); });
lookup.Add(typeof(int), x => { return int.Parse(x); });
lookup.Add(typeof(double), x => { return double.Parse(x); });
var paramExpr = Expression.Parameter(typeof(Vehicle), "VehicleNo");
var keyPropExpr = Expression.Property(paramExpr, "VehicleNo");
if (!lookup.ContainsKey(keyPropExpr.Type))
throw new Exception("Unknown type : " + keyPropExpr.Type.ToString());
var typeDelegate = lookup[keyPropExpr.Type];
var constantExp = typeDelegate("A123");
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(constantExp));
var condExpr = Expression.Lambda<Func<SysClientSiteUser, bool>>(eqExpr, paramExpr);
query = query.Where(condExpr);
for normal field, it's working. But if I want to call Driver name. it's not work. How to call "Driver.Name"?
You can use a helper function to convert a nested property name string to an Expression that accesses that property for a given ParameterExpression and type:
private static Expression MakePropertyExpression<T>(string propertyName, Expression baseExpr) =>
propertyName.Split('.').Aggregate(baseExpr, (b, pname) => Expression.Property(b, pname));
I work with Dapper and I try to create auto-mapped method for inner join.
This is the example of the models:
public class User
{
public long IdUser { get; set; }
public string Email { get; set; }
}
public class Page
{
public long Id { get; set; }
public string Name { get; set; }
public long IdUserCreatedPage { get; set; }
public User UserCreatedPage { get; set; }
}
This is the query:
SELECT * FROM "PAGE" INNER JOIN "USER" ON "PAGE"."IdUserCreatedPage" = "USER"."IdUser"
if I write code mannualy I will write this:
public List<Page> GetPage(IDbConnection dbConnection, string sql)
{
return (List<Page>)dbConnection.Query<Page, User, Page>(sql,
(Page p, User u) =>
{
p.UserCreatedPage = u;
return p;
},
splitOn: "IdUser").ToList();
}
Now, what I want is create dynamically the Func<TFirst, TSecond, TOut> that I need for mapping the object.
Can someone help me? Thank you very much.
P.S. I know that in this case it does not make sense create it dynamically, but this it's only a simply version of all the project of auto-mapping.
SOLUTION
Finally I find the way to do what I want.
This is the code of the function that Generate Func<TFirst, TSecond, TOut>:
public static Func<TFirst, TSecond, TFirst> MappingDynamicFunc<TFirst, TSecond>()
{
ParameterExpression paramFirst = Expression.Parameter(typeof(TFirst), "paramFirst");
ParameterExpression paramSecond = Expression.Parameter(typeof(TSecond), "paramSecond");
MemberExpression memberExpression = Expression.PropertyOrField(paramFirst, "UserCreatedPage");
BinaryExpression assign = Expression.Assign(memberExpression, paramSecond);
LabelTarget labelTarget = Expression.Label(typeof(TFirst));
GotoExpression returnExpression = Expression.Return(labelTarget, paramFirst, typeof(TFirst));
LabelExpression labelExpression = Expression.Label(labelTarget, Expression.Default(typeof(TFirst)));
BlockExpression block = Expression.Block(
assign,
returnExpression,
labelExpression
);
return Expression.Lambda<Func<TFirst, TSecond, TFirst>>(block, new ParameterExpression[] { paramFirst, paramSecond }).Compile();
}
And this is the "GetPage" method:
public List<Page> GetPage(IDbConnection dbConnection, string sql)
{
return (List<Page>)dbConnection.Query<Page, User, Page>(sql,
MappingDynamicFunc<Page, User>(),
splitOn: "IdUser").ToList();
}
Take a look at PredicateBuilder. http://www.albahari.com/nutshell/predicatebuilder.aspx
Here is some pseudo code
var predicate = PredicateBuilder.True<SomeClass>();
if (SomeCondition)
{
var inner = PredicateBuilder.False<SomeClass>();
inner = inner.Or(p => p.Category == "WhatEver");
inner = inner.Or(p => p.Category == "");
predicate = predicate.And(inner);
}
...
var result = MyIEnumerable<SomeClass>.AsQueryable()
.Where(predicate)
.FirstOrDefault();
Piggybacking off of a very similar question...
I need to generate an Expression from a ViewModel to pass as a search predicate for IQueryable.Where. I need to be able to include/exclude query parameters based on what is provided by the user. Example:
public class StoresFilter
{
public int[] Ids { get; set; }
[StringLength(150)]
public string Name { get; set; }
[StringLength(5)]
public string Abbreviation { get; set; }
[Display(Name = "Show all")]
public bool ShowAll { get; set; } = true;
public Expression<Func<Store, bool>> ToExpression()
{
List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>();
if (Ids != null && Ids.Length > 0)
{
expressions.Add(x => Ids.Contains(x.Id));
}
if (Name.HasValue())
{
expressions.Add(x => x.Name.Contains(Name));
}
if (Abbreviation.HasValue())
{
expressions.Add(x => x.Abbreviation.Contains(Abbreviation));
}
if (!ShowAll)
{
expressions.Add(x => x.Enabled == true);
}
if (expressions.Count == 0)
{
return x => true;
}
// how to combine list of expressions into composite expression???
return compositeExpression;
}
}
Is there a simple way to build a composite expression from a list of expressions? Or do I need to go through the process of manually building out the expression using ParameterExpression, Expression.AndAlso, ExpressionVisitor, etc?
You should not build and combine Expressions, but instead of this you should do it through IQuerable<Store> via .Where chain. Moreover, source.Expression will contain desired expression:
public IQueryable<Store> ApplyFilter(IQueryable<Store> source)
{
if (Ids != null && Ids.Length > 0)
source = source.Where(x => Ids.Contains(x.Id));
if (Name.HasValue())
source = source.Where(x => x.Name.Contains(Name));
if (Abbreviation.HasValue())
source = source.Where(x => x.Abbreviation.Contains(Abbreviation));
if (!ShowAll)
source = source.Where(x => x.Enabled == true);
//or return source.Expression as you wanted
return source;
}
Usage:
var filter = new StoresFilter { Name = "Market" };
var filteredStores = filter.ApplyFilter(context.Stores).ToList();
void Main()
{
var store = new Store
{
Id = 1,
Abbreviation = "ABC",
Enabled = true,
Name = "DEF"
};
var filter = new Filter<Store>
{
Ids = new HashSet<int>(new [] {1,2,3,4}),
Abbreviation = "GFABC",
Enabled = true,
Name = "SDEFGH",
ShowAll = false
}
var expression = filter.ToExpression(store);
var parameterType = Expression.Parameter(typeof(Store), "obj");
// Generate Func from the Expression Tree
Func<Store,bool> func = Expression.Lambda<Func<Store,bool>>(expression,parameterType).Compile();
}
public class Store
{
public int Id {get; set;}
public string Name {get; set;}
public string Abbreviation { get; set; }
public bool Enabled { get; set; }
}
public class Filter<T> where T : Store
{
public HashSet<int> Ids { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
public bool Enabled {get; set;}
public bool ShowAll { get; set; } = true;
public Expression ToExpression(T data)
{
var parameterType = Expression.Parameter(typeof(T), "obj");
var expressionList = new List<Expression>();
if (Ids != null && Ids.Count > 0)
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Id");
ConstantExpression idConstantExpression = Expression.Constant(data.Id, typeof(int));
MethodInfo filtersMethodInfo = typeof(HashsetExtensions).GetMethod("Contains", new[] { typeof(HashSet<int>), typeof(int) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!string.IsNullOrEmpty(Name))
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Name");
ConstantExpression idConstantExpression = Expression.Constant(data.Name, typeof(string));
MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!string.IsNullOrEmpty(Abbreviation))
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Abbreviation");
ConstantExpression idConstantExpression = Expression.Constant(data.Abbreviation, typeof(string));
MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!ShowAll)
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Enabled");
var binaryExpression = Expression.Equal(idExpressionColumn, Expression.Constant(true, typeof(bool)));
expressionList.Add(binaryExpression);
}
if (expressionList.Count == 0)
{
expressionList.Add(BinaryExpression.Constant(true));
}
// Aggregate List<Expression> data into single Expression
var returnExpression = expressionList.Skip(1).Aggregate(expressionList.First(), (expr1,expr2) => Expression.And(expr1,expr2));
return returnExpression;
// Generate Func<T,bool> - Expression.Lambda<Func<T,bool>>(returnExpression,parameterType).Compile();
}
}
public static class StringExtensions
{
public static bool Contains(this string source, string subString)
{
return source?.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
public static class HashsetExtensions
{
public static bool Contains(this HashSet<string> source, string subString)
{
return source.Contains(subString,StringComparer.OrdinalIgnoreCase);
}
}
How it works ?
Only in simple equality cases you can use BinaryExpression like Expression.Equal, Expression.GreaterThan, which is shown for the property like "ShowAll"
For other cases like string / Array / List Contains, you need extension method, which can take two types and provide the result. A separate Contains for string to make it case neutral. Also for collection Hashset has a better choice, it has O(1) time complexity, unlike O(N) for an array
We use MethodCallExpression to call the extension methods
Finally we aggreagte all the expressions, which can be compiled to create Func<T,bool>
In case you need something like x => true, then BinaryExpression.Constant(true) is sufficient
I have provided a Sample implementation using the Store class that you have defined
I have a LINQ expression that I wish to make it become generic :
public class GenericBudgetMatrix
{
public List<string> MatrixHeaders { set; get; }
public List<GenericBudgetMatrixRow> MatrixRow { set; get; }
public GenericBudgetMatrixRow MatrixFooterRow { set; get; }
}
public class GenericBudgetMatrixRow
{
public string EntityName { set; get; }
public List<decimal> Values { set; get; }
public decimal Total { set; get; }
}
GenericBudgetMatrix _genericBudgetMatrix = new GenericBudgetMatrix();
_genericBudgetMatrix.MatrixRow = _matrixTemplate.AsEnumerable().Select(r =>
new GenericBudgetMatrixRow
{
EntityName = r.Field<string>(0),
Values = r.ItemArray.Skip(1).Select(x => Convert.ToDecimal(x)).ToList(),
Total = r.ItemArray.Skip(1).Select(x => Convert.ToDecimal(x)).Sum()
}
).ToList();
I wish to make a new method to accept
r =>
new GenericBudgetMatrixRow
{
EntityName = r.Field<string>(0),
Values = r.ItemArray.Skip(1).Select(x =>
Convert.ToDecimal(x)).ToList(),
Total = r.ItemArray.Skip(1).Select(x =>
Convert.ToDecimal(x)).Sum()
}
as a Func expression parameter.
Something like this :
public void GenericMethod(Func<expression> predicate, DataTable _matrixTemplate)
{
_matrixTemplate.AsEnumerable().Select(predicate).ToList();
}
Any ideas that can help me build this approach?
Select expects a Func<T, TResult>, so you should provide it as parameter to your method:
public void GenericMethod<T, TResult>(Func<T, TResult> predicate, DataTable _matrixTemplate)
{
_matrixTemplate.AsEnumerable().Select(predicate).ToList();
}
Furthermore I guess your method should return the result of your selection, so turn it into a method returning instances of TResult(which in your case is GenericBudgetMatrixRow). Last but not least the call to ToList is obsolete as you´re doing this in your calling code anyway. Thus return an IEnumerable<TResult> instead:
public IEnumerable<TResult> GenericMethod<T, TResult>(Func<T, TResult> predicate, DataTable _matrixTemplate)
{
return _matrixTemplate.AsEnumerable().Select(predicate);
}
Now you cann call it like this:
_genericBudgetMatrix.MatrixRow = GenericMethod(
r => new GenericBudgetMatrixRow
{
EntityName = r.Field<string>(0),
Values = r.ItemArray.Skip(1).Select(x => Convert.ToDecimal(x)).ToList(),
Total = r.ItemArray.Skip(1).Select(x => Convert.ToDecimal(x)).Sum()
},
_matrixTemplate)
.ToList();
I figured out the solution already. It is due to the Generic Type
public IEnumerable<TResult> GenericMethod(Func<DataRow, TResult> predicate, DataTable _matrixTemplate)
{
return _matrixTemplate.AsEnumerable().Select(predicate);
}