Convert this LINQ to dynamic Expression tree - c#

I have an extension in my project that let's me sort an IEnumerable with a string, so that sorting can be done more dynamically.
So, if I have these models:
public MyModel
{
public int Id {get; set;}
public string RecordName {get; set;}
public ChildModel MyChildren {get; set;}
}
public ChildModel
{
public int ChildModelId {get; set;}
public string ChildName {get; set;}
public DateTime SavedDate {get; set;}
}
I could sort my list like this:
var myList = db.MyModel.Where(m => m.IsActive);
myList
.OrderBy(m => m.MyChildren
.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
or:
var myList = db.MyModel.Where(m => m.IsActive);
myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate);
But I want to be able to dynamically sort, based on user options. So I would like this:
var myList = db.MyModel.Where(m => m.IsActive);
myList.OrderByField("MyChildren.SavedDate");
The extension method I have so far looks like this:
public static class MkpExtensions
{
public static IEnumerable<T> OrderByField<T>(this IEnumerable<T> list, string sortExpression)
{
sortExpression += "";
string[] parts = sortExpression.Split(' ');
bool descending = false;
string fullProperty = "";
if (parts.Length > 0 && parts[0] != "")
{
fullProperty = parts[0];
if (parts.Length > 1)
{
descending = parts[1].ToLower().Contains("esc");
}
ParameterExpression inputParameter = Expression.Parameter(typeof(T), "p");
Expression propertyGetter = inputParameter;
foreach (string propertyPart in fullProperty.Split('.'))
{
var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName);
.Any(x => x.IsGenericType &&
(x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
if (checkIfCollection)
{
var pgType = propertyGetter.Type;
var childType = pgType.GetGenericArguments().Single();
var childProp = childType.GetProperty(propertyPart);
ParameterExpression childInParam = Expression.Parameter(childType, "c");
var propertyAccess = Expression.Property(childInParam, childProp);
var orderByExp = Expression.Lambda(propertyAccess, childInParam);
// At this point, orderByExp is c => c.ActionDate
// Now I want to build the expression tree to handle the order by
XXXXX This is where I need help.
}
else
{
// This handles a singular property. Like "MyChildren.ChildName"
// and this part does work
PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
if (prop == null)
throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
propertyGetter = Expression.Property(propertyGetter, prop);
}
}
Expression conversion = Expression.Convert(propertyGetter, typeof(object));
var getter = Expression.Lambda<Func<T, object>>(conversion, inputParameter).Compile();
if (descending)
{
// This would be like
// list.OrderByDescending(m => m.MyChildren
// .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
return list.OrderByDescending(getter);
}
else
{
// This would be like
// list.OrderBy(m => m.MyChildren
// .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
return list.OrderBy(getter);
}
}
return list;
}
}

Basically you should use the following Expression.Call overload which allows you to build an expression for calling static generic methods (what are all the LINQ extension methods).
To build the equivalent of expression like this
m => m.MyChildren.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate
you can use the following snippet:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
var firstOrDefaultCall = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { childType },
orderByDescendingCall
);
propertyGetter = Expression.Property(firstOrDefaultCall, childProp);
But note that you'll get NRE if the collection is empty.
So you'd better build an expression like this:
m => m.MyChildren.OrderByDescending(c => c.SavedDate)
.Select(c => (DateTime?)c.SavedDate).FirstOrDefault()
with:
// At this point, orderByExp is c => c.ActionDate
var orderByDescendingCall = Expression.Call(
typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type },
propertyGetter, orderByExp
);
Expression propertySelector = propertyAccess;
// If value type property and not nullable, convert it to nullable
if (propertySelector.Type.IsValueType && Nullable.GetUnderlyingType(propertySelector.Type) == null)
propertySelector = Expression.Convert(propertySelector, typeof(Nullable<>).MakeGenericType(propertySelector.Type));
var selectCall = Expression.Call(
typeof(Enumerable), "Select", new Type[] { childType, propertySelector.Type },
orderByDescendingCall, Expression.Lambda(propertySelector, childInParam)
);
propertyGetter = Expression.Call(
typeof(Enumerable), "FirstOrDefault", new Type[] { propertySelector.Type },
selectCall
);

Related

set nested properties of object while mapping

I am using an expression tree as an alternative to automapper to map source properties to target properties using below code
What i am doing is, I have created static method inside static class for mapping and assigning inner child object property to outer object property
public static class PropertyMapper<TSource, TDest>
{
private static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> _mappingExpression;
private static Func<TSource, Dictionary<string, MasterSection>, TDest> _mapper;
static PropertyMapper()
{
_mappingExpression = ProjectionMap();
_mapper = _mappingExpression.Compile();
}
public static Func<TSource, Dictionary<string, MasterSection>, TDest> Mapper => _mapper;
public static Expression<Func<TSource, Dictionary<string, MasterSection>, TDest>> ProjectionMap()
{
var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
var targetProperties= typeof(TDest).GetProperties().Where(p => p.CanWrite);
var propertyMap =
from d in targetProperties
join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
where d.Name != "SourceOfDataId" && d.Name!= "SourceOfData"
select new { Source = s, Dest = d };
var itemParam = Expression.Parameter(typeof(TSource), "item");
var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source))).ToList();
var sourceOfDataIdProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfDataId");
if (sourceOfDataIdProp != null)
{
memberBindings.Add(Expression.Bind(sourceOfDataIdProp,Expression.Convert(Expression.Property(Expression.Property(itemParam, "SourceOfData"),"Id"),typeof(Guid?))));
}
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here i would like to update `sourceOfData` object property "isApproved"
}
var newExpression = Expression.New(typeof(TDest));
var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
var projection = Expression.Lambda<Func<TSource, Dictionary<string, MasterSection>, TDest>>(memberInitExpression, itemParam, dictParam);
return projection;
}
}
and i am using the above method here in below to map the source properties to target properties
AirflowsLab = sourceMechanicalData.AirflowsLab.Select(a => PropertyMapper<LibraryLabAirflow, LibraryLabAirflow>.Mapper(a, masterSectionMappedLibrary)).ToList();
and the structure for LibraryLabAirflow is looks like as below
public class LibraryLabAirflow
{
[ForeignKey("SourceOfData")]
public Guid? SourceOfDataId { get; set; }
public virtual CodeStandardGuideline SourceOfData { get; set; }
}
Above mapping is working fine, what i am trying is now that i need to access the sourceOfData child object of target and update the property for sourceOfData and map that updated child object to source child object sourceOfData.
below is the sourceOfData object details
"SourceOfData":{
"Id": "c5bf3585-50b1-4894-8fad-0ac884343935",
"IsApproved": null, // trying to set this to true instead of null inside target object
"MasterSection": null
},
I am not sure how to access the child object property using an expression tree in this scenario and i cannot use automapper library. Could any one please let me know how to access the child object property and update and assign back to target.
What i am trying to generate an expression looks like this source.SourceOfData = target.SourceOfData but before this i need to update one of the property of target.SourceOfData
Many thanks in advance
Desired expression :
AirflowsLab = sourceMechanicalData.AirflowsLab.Where(a => a != null).Select(item => new LibraryLabAirflow()
{
SourceOfData = new CodeStandardGuideline()
{
IsApproved = true,// trying to set this through expression tree
City = item.SourceOfData.City
......
.......
}
}).ToList(),
trying like this is not working as well, 1
var sourceOfDataProp = targetProperties.FirstOrDefault(s => s.Name == "SourceOfData");
if(sourceOfDataProp != null)
{
// here need to get the sourceofdata properties
var sourceOfDataProperty = Expression.Property(Expression.Constant(sourceOfDataProp), "IsApproved");
}
Update:
i have implemented the logic inside if block of sourceOfDataProp != null but getting an error
if (sourceOfDataProp != null)
{
var targetitemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourcePropertyInfo = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetitemParam, typeof(TTarget).GetProperty("SourceOfData"));
var targetPropertyInfo = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = from tp in targetPropertyInfo
join sp in sourcePropertyInfo
on new { tp.Name, tp.PropertyType } equals new { sp.Name, sp.PropertyType }
where !sourceOfDataPropertiesFilter.Contains(tp.Name)
select new { Source = sp, Target = tp };
// getting error at below line type of arguments does not match
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.PropertyOrField(targetitemParam, "SourceOfData"))).ToList();
}
I have solved this problem like as below
if (sourceOfDataProp != null)
{
var targetItemParam = Expression.Parameter(typeof(TTarget), "item");
var sourceOfDataPropertiesFilter = new List<string>()
{
"IsApproved"
};
var sourceItem = Expression.Property(itemParam, typeof(TSource).GetProperty("SourceOfData"));
var sourceOfDataSourceProperties = sourceItem.Type.GetProperties().Where(p => p.CanRead);
var targetItem = Expression.Property(targetItemParam, typeof(TTarget).GetProperty("SourceOfData"));
var sourceOfDataTargetProperties = targetItem.Type.GetProperties().Where(p => p.CanWrite);
var sourceOfDataPropertyMap = sourceOfDataTargetProperties.Join(sourceOfDataSourceProperties,
t => new { t.Name, t.PropertyType },
s => new { s.Name, s.PropertyType },
(t, s) => new { Source = s, Target = t })
.Where(t => !sourceOfDataPropertiesFilter.Contains(t.Target.Name));
var sourceOfDataMemberBindings = sourceOfDataPropertyMap.Select(p => Expression.Bind(p.Target, Expression.Property(sourceItem, p.Source))).ToList();
var sourceOfDataIsApprovedProp = sourceOfDataTargetProperties.FirstOrDefault(s => s.Name == "IsApproved");
if (sourceOfDataIsApprovedProp != null)
{
sourceOfDataMemberBindings.Add(Expression.Bind(sourceOfDataIsApprovedProp, Expression.Constant(true, typeof(bool?))));
}
var sourceOfDataExpression = Expression.New(typeof(DesignHub.Entities.CodeStandardGuideline));
var sourceOfDataMemberInitExpression = Expression.MemberInit(sourceOfDataExpression, sourceOfDataMemberBindings);
memberBindings.Add(Expression.Bind(sourceOfDataProp, sourceOfDataMemberInitExpression));
}

Linq orderbys from collection [duplicate]

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
How do I specify the argument passed to orderby using a value I take as a parameter?
Ex:
List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}
Currently implementation:
List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();
Instead of c.Address, how can I take that as a parameter?
Example
string param = "City";
List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();
You can use a little bit of reflection to construct the expression tree as follows (this is an extension method):
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
orderByProperty is the Property name you want to order by and if pass true as parameter for desc, will sort in descending order; otherwise, will sort in ascending order.
Now you should be able to do existingStudents.OrderBy("City",true); or existingStudents.OrderBy("City",false);
Here's a possiblity using reflection...
var param = "Address";
var propertyInfo = typeof(Student).GetProperty(param);
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
To expand on the answer by #Icarus: if you want the return type of the extension method to be an IOrderedQueryable instead of an IQueryable, you can simply cast the result as follows:
public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
string command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
1) Install System.Linq.Dynamic
2) Add the following code
public static class OrderUtils
{
public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
{
var str = expression.Body.ToString();
var param = expression.Parameters.First().Name;
str = str.Replace("Convert(", "(").Replace(param + ".", "");
return str + (isDesc ? " descending" : "");
}
}
3) Write your switch for selecting of Lambda function
public static class SortHelper
{
public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
{
orderProperty = orderProperty?.ToLowerInvariant();
switch (orderProperty)
{
case "firstname":
return x => x.PersonalInfo.FirstName;
case "lastname":
return x => x.PersonalInfo.LastName;
case "fullname":
return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
case "email":
return x => x.Email;
}
}
}
4) Use your helpers
Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())
5) You can use it with pagging (PagedList)
public virtual IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
params Expression<Func<T, object>>[] includes)
{
var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
var query = orderedQueryable.Where(where).GetPage(page);
query = AppendIncludes(query, includes);
var results = query.ToList();
var total = Dbset.Count(where);
return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
}
Explanation
System.Linq.Dynamic allows us to set string value in OrderBy method. But inside this extension the string will be parsed to Lambda. So I thought it would work if we will parse Lambda to string and give it to OrderBy method. And it works!
Here's something I came up with for dealing with a conditional Descending. You could combine this with other methods of generating the keySelector func dynamically.
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
System.ComponentModel.ListSortDirection sortOrder
)
{
if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
return source.OrderBy(keySelector);
else
return source.OrderByDescending(keySelector);
}
Usage:
//imagine this is some parameter
var direction = System.ComponentModel.ListSortDirection.Ascending;
query = query.OrderBy(ec => ec.MyColumnName, direction);
Notice this allows you to chain this .OrderBy extension with a new parameter onto any IQueryable.
// perhaps passed in as a request of user to change sort order
// var direction = System.ComponentModel.ListSortDirection.Ascending;
query = context.Orders
.Where(o => o.Status == OrderStatus.Paid)
.OrderBy(ec => ec.OrderPaidUtc, direction);
private Func<T, object> GetOrderByExpression<T>(string sortColumn)
{
Func<T, object> orderByExpr = null;
if (!String.IsNullOrEmpty(sortColumn))
{
Type sponsorResultType = typeof(T);
if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
{
System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
orderByExpr = (data => pinfo.GetValue(data, null));
}
}
return orderByExpr;
}
public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
{
return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
}
// Call the code like below
var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);
var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);
This doesn't let you pass a string, as you asked for in your question, but it might still work for you.
The OrderByDescending method takes a Func<TSource, TKey>, so you can rewrite your function this way:
List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
{
return existingStudents.OrderByDescending(orderBy).ToList();
}
There are other overloads for OrderByDescending as well that take a Expression<Func<TSource, TKey>>, and/or a IComparer<TKey>. You could also look into those and see if they provide you anything of use.
The only solution that worked for me was posted here https://gist.github.com/neoGeneva/1878868 by neoGeneva.
I will re-post his code because it works well and I wouldn't want it to be lost in the interwebs!
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
{
if (source == null)
throw new ArgumentNullException("source", "source is null.");
if (string.IsNullOrEmpty(sortExpression))
throw new ArgumentException("sortExpression is null or empty.", "sortExpression");
var parts = sortExpression.Split(' ');
var isDescending = false;
var propertyName = "";
var tType = typeof(T);
if (parts.Length > 0 && parts[0] != "")
{
propertyName = parts[0];
if (parts.Length > 1)
{
isDescending = parts[1].ToLower().Contains("esc");
}
PropertyInfo prop = tType.GetProperty(propertyName);
if (prop == null)
{
throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
}
var funcType = typeof(Func<,>)
.MakeGenericType(tType, prop.PropertyType);
var lambdaBuilder = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
.MakeGenericMethod(funcType);
var parameter = Expression.Parameter(tType);
var propExpress = Expression.Property(parameter, prop);
var sortLambda = lambdaBuilder
.Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });
var sorter = typeof(Queryable)
.GetMethods()
.FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { tType, prop.PropertyType });
return (IQueryable<T>)sorter
.Invoke(null, new object[] { source, sortLambda });
}
return source;
}
Add the nugget package Dynamite to your code
Add the namespace Dynamite.Extensions
Eg : using Dynamite.Extensions;
Give Order by query like any SQL query
Eg : students.OrderBy(" City DESC, Address").ToList();
To extend the response of #Icarus: if you want to sort by two fields I could perform the following function (for one field the response of Icarius works very well).
public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "p");
var body = GetBodyExp(SortField1, SortField2, param);
var exp = Expression.Lambda(body, param);
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
This is the function that the body returns for the lambda expression, it works with string and int, but it is enough to add more types to make it work according to the need of each programmer
public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
{
// SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE
string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
string TypeName2 = Expression.Property(Parametro, field2).Type.Name;
// SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
Type TypeAnonymous = null;
if (TypeName1 == "String")
{
string var1 = "0";
if (TypeName2 == "Int32")
{
int var2 = 0;
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
if (TypeName2 == "String")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
}
if (TypeName1 == "Int32")
{
int var1 = 0;
if (TypeName2 == "Int32")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
if (TypeName2 == "String")
{
string var2 = "0";
var example = new { var1, var2 };
TypeAnonymous = example.GetType();
}
}
//se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);
//BODY
NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));
return body;
}
to use it the following is done
IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();
if there is a better way to do this, it would be great if they share it
I managed to solve it thanks to: How can I make a Multiple property lambda expression with Linq
New Answer : this is a more complete answer that supports multiple columns for order by like SQL. Example : .OrderBy("FirstName,Age DESC") :
namespace Utility;
public static class QueryExtension
{
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc, bool isThenBy = false)
{
string command = isThenBy ? (desc ? "ThenByDescending" : "ThenBy") : (desc ? "OrderByDescending" : "OrderBy");
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sqlOrderByList)
{
var ordebyItems = sqlOrderByList.Trim().Split(',');
IQueryable<TEntity> result = source;
bool useThenBy = false;
foreach (var item in ordebyItems)
{
var splt = item.Trim().Split(' ');
result = result.OrderBy(splt[0].Trim(), (splt.Length > 1 && splt[1].Trim().ToLower() == "desc"), useThenBy);
if (useThenBy)
useThenBy = true;
}
return result;
}
}
The second function iterates over orderby columns and uses the first one.
Use it like this :
using Utility;
...
public void MyMethod()
{
var query = _dbContext.Person.AsQueryable();
query.OrderBy("FirstName,Age DESC");
}
I'm way late to the party but none of these solutions worked for me. I was eager to try System.Linq.Dynamic, but I couldn't find that on Nuget, maybe depreciated? Either way...
Here is a solutions I came up with. I needed to dynamically use a mixture of OrderBy, OrderByDescending and OrderBy > ThenBy.
I simply created an extension method for my list object, a bit hacky I know... I wouldn't recommend this if it were something I was doing a lot of, but it's good for a one off.
List<Employee> Employees = GetAllEmployees();
foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
{
//do stuff
}
public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
{
switch (eEmployeeSort)
{
case Enums.EmployeeSort.Name_ASC:
return lEmployees.OrderBy(x => x.Name);
case Enums.EmployeeSort.Name_DESC:
return lEmployees.OrderByDescending(x => x.Name);
case Enums.EmployeeSort.Department_ASC_Salary_DESC:
return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
default:
return lEmployees.OrderBy(x => x.Name);
}
}

Dynamic linq expression tree with nested properties

I have a list which I must filter on child properties. The filter operator is dynamic and I'm using a predicate builder in order to combine several filters/lambdas.
For simplicity, let's say that I have two classes like this:
public class FirstClass
{
public int Id { get; set; }
public ICollection<SecondClass> MyList { get; set; }
}
public class SecondClass
{
public int ReferenceId { get; set; }
public int Value { get; set; }
}
My filter use a reference id, an operator type and a value, such that a pseudo code would be like this:
"list of FirstClass".Where(param1 =>
param1.MyList.Single(param2 =>
param2.ReferenceId == "reference id").Value "operatorType" "value")
The actual filter will be something like 123 eq 456, where the reference id is 123, operatorType is "eq" and value is 456.
If the operator just was equality, then the following works just fine:
Expression<Func<FirstClass, bool>> lambda =
param1 => param1.MyList.Single(param2 => param2.ReferenceId == id).Value == value;
Also, filtering only on FirstClass with dynamic expressions, works like a charm, e.g. filtering on Id (my ExpressionTypeDictionary is a dictionary for selecting an ExpressionType based on the provided operatorType):
var parameter = Expression.Parameter(typeof(FirstClass), "param1");
Expression body = parameter;
body = Expression.Property(body, "Id");
body = Expression.MakeBinary(ExpressionTypeDictionary[operatorType], body, value);
var lambda = Expression.Lambda<Func<FirstClass, bool>>(body, new[] { parameter });
I'm able to get the following to compile, but executing the filter on real data using EF Core returns an exception for querySource:
var parameter = Expression.Parameter(typeof(FirstClass), "param1");
Expression<Func<FirstClass, int>> left = param1 =>
param1.MyClass.Single(param2 => param2.ReferenceId == id).Value;
var body = Expression.MakeBinary(
ExpressionTypeDictionary[operatorType],
left.Body,
Expression.Constant(value));
var lambda = Expression.Lambda<Func<FirstClass, bool>>(body, new[] { parameter });
...
theList.Where(lambda);
Any suggestions are appreciated :)
I think rather than expression like this
Expression<Func<FirstClass, bool>> predicate =
x => x.MyList.Single(y => y.ReferenceId == id).Value [operator] value;
you'd better build an expression like this:
Expression<Func<FirstClass, bool>> predicate =
x => x.MyList.Any(y => y.ReferenceId == id && y.Value == value);
Here is how you can do that:
var innerParameter = Expression.Parameter(typeof(SecondClass), "y");
var innerPredicate = Expression.Lambda<Func<SecondClass, bool>>(
Expression.AndAlso(
Expression.Equal(Expression.Property(innerParameter, "ReferenceId"), Expression.Constant(id)),
Expression.MakeBinary(ExpressionTypeDictionary[operatorType], Expression.Property(innerParameter, "Value"), Expression.Constant(value))),
innerParameter);
var parameter = Expression.Parameter(typeof(FirstClass), "x");
var predicate = Expression.Lambda<Func<FirstClass, bool>>(
Expression.Call(
typeof(Enumerable), "Any", new Type[] { typeof(SecondClass) },
Expression.Property(parameter, "MyList"), innerPredicate),
parameter);

Dynamically build an Expression tree for sorting

I have an extension in my project that let's me sort an IEnumerable with a string, so that sorting can be done more dynamically.
So, if I have these models:
public MyModel
{
public int Id {get; set;}
public string RecordName {get; set;}
public ChildModel MyChild {get; set;}
}
public ChildModel
{
public int ChildModelId {get; set;}
public DateTime SavedDate {get; set;}
}
I can sort two ways:
myList.OrderByField("RecordName ");
myList.OrderByField("MyChild.SavedDate");
However, if my object has an ICollection property, like ICollection<ChildModel> MyChildren I can hard code my sort like this:
myList
.OrderBy(m => m.MyChildren
.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate);
And get what I want.
My question is, how can I update my extension method to allow to get the same results with this:
myList.OrderByField("MyChildren.SavedDate");
Here is my current extension:
public static class MkpExtensions
{
public static IEnumerable<T> OrderByField<T>(this IEnumerable<T> list, string sortExpression)
{
sortExpression += "";
string[] parts = sortExpression.Split(' ');
bool descending = false;
string fullProperty = "";
if (parts.Length > 0 && parts[0] != "")
{
fullProperty = parts[0];
if (parts.Length > 1)
{
descending = parts[1].ToLower().Contains("esc");
}
ParameterExpression inputParameter = Expression.Parameter(typeof(T), "p");
Expression propertyGetter = inputParameter;
foreach (string propertyPart in fullProperty.Split('.'))
{
PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
if (prop == null)
throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
propertyGetter = Expression.Property(propertyGetter, prop);
}
Expression conversion = Expression.Convert(propertyGetter, typeof(object));
var getter = Expression.Lambda<Func<T, object>>(conversion, inputParameter).Compile();
if (descending)
return list.OrderByDescending(getter);
else
return list.OrderBy(getter);
}
return list;
}
}
I was thinking of checking the type of the prop and doing an if... else statement, but I'm not sure.
Maybe something like this:
foreach (string propertyPart in fullProperty.Split('.'))
{
var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName);
.Any(x => x.IsGenericType &&
(x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
if (checkIfCollection)
{
// Can I get this to do something like
// myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate));
// So far, I can get the propertyGetter type, and the type of the elements:
var pgType = propertyGetter.Type;
var childType = pgType.GetGenericArguments().Single();
// Now I want to build the expression tree to get the max
Expression left =
Expression.Call(propertyGetter, pgType.GetMethod("Max", System.Type.EmptyTypes));
// But pgType.GetMethod isn't working
}
else
{
PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart);
if (prop == null)
throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'");
propertyGetter = Expression.Property(propertyGetter, prop);
}
}
The Max function is an extension method, not a member method of IEnumerable or ICollection. You must call it from it's class Enumerable.
Here is an example of how to call Max through expression tree:
IEnumerable<int> list = new List<int> { 3, 5, 7, 2, 12, 1 };
var type = typeof(Enumerable); //This is the static class that contains Max
//Find The overload of Max that matches the list
var maxMethod = type.GetMethod("Max", new Type[] { typeof(IEnumerable<int>) });
ParameterExpression p = Expression.Parameter(typeof(IEnumerable<int>));
//Max is static, so the calling object is null
var exp = Expression.Call(null, maxMethod, p);
var lambda = Expression.Lambda<Func<IEnumerable<int>, int>>(exp, p);
Console.WriteLine(lambda.Compile()(list));

Expression.Call and Count

I'm looking for a way to do following dynamically:
var q = context.Subscription
.Include("Client")
.Include("Invoices")
Where(s=>s.Client.Invoices.Count(i=>i.InvoiceID == SomeInt) > 0);
I would like to build expression dynamically for the left side:
Expression left = s => s.Client.Invoices.Count(i => i.InvoiceID == iSomeVar); //!
Expression right = Expression.Constant(0);
var binary = Expression.GreaterThan(left, right);
Thanks!
UPDATED NOTES:
Please note: The end result must be
Expression<Func<T, bool>>
Simple version:
// To give clear idea, all what I want to achieve is to determine
// whether specific record exists in reference table using known Path.
// Ultimately I want to extend following function (which works great by
// the way, but for simple operations)
static Expression CreateExpression<T>(string propertyPath,
object propertyValue,
ParameterExpression parameterExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
MemberExpression left = Expression.Property(parameterExpression, property);
ConstantExpression right = Expression.Constant(0);
BinaryExpression binary = Expression.GreaterThan(left, right);
return binary;
}
// And I want to call this function and get result exactly as shown below:
Expression result =
CreateExpression<Subscription>("Client.Invoices.InvoiceID",
theID,
valueSelector.Parameters.Single());
// Where result will be:
// t => t.Client.Invoices.Count(i => i.InvoiceID == theID) > 0;
Extended version:
// 1) I'm using Silverlight 4, EF, RIA.
// 2) At the server side I have a function GetSubscriptionsByCriteria
// that looks about it:
public IQueryable<Subscription> GetSubscriptionsByCriteria(...)
{
var query = this.ObjectContext.Subscriptions.Include("Client")
.Include("Client.Invoices");
var criteria = BuildCriteria(...);
return query.Where(criteria)
}
// 3) BuildCriteria(...) function gathers Expressions and
// aggregates it into the single Expression with different
// AND/OR conditions, something like that:
public Expression<Func<Subscription, bool>> BuildCriteria(
List<SearchFilter> filters,
Expression<Func<Subscription, bool>> valueSelector)
{
List<Expression> filterExpressions = new List<Expression>();
...
Expression expr = CreateExpression<Subscription>(
sfItem.DBPropertyName,
sfItem.DBPropertyValue,
paramExpression,
sf.SearchCondition);
filterExpressions.Add(expr);
...
var filterBody =
filterExpressions.Aggregate<Expression>(
(accumulate, equal) => Expression.And(accumulate, equal));
return Expression
.Lambda<Func<Subscription, bool>>(filterBody, paramExpression);
}
// 4) Here is the simplified version of CreateExpression function:
static Expression CreateExpression<T>(string propertyName,
object propertyValue,
ParameterExpression paramExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
ConstantExpression right = Expression.Constant(0);
MemberExpression left = Expression.Property(paramExpression, property);
return binary = Expression.Equals(left, right);
}
So, I hope it's clear now why do I need Expression for the left side in my original post. Trying to make this as DRY as possible.
P.S. Not to make it too confusing here is why I think I need to do ёExpression.Call(...)ё:
When I run following code and break it to see DebugView I notice this:
Expression<Func<Subscription, bool>> predicate =
t => t.Client.Invoices.Count(i => i.InvoiceID == 5) > 0;
BinaryExpression eq = (BinaryExpression)predicate.Body;
var left = eq.Left; // <-- See DEBUG VIEW
var right = eq.Right;
// DEBUG VIEW:
// Arguments: Count = 2
// [0] = {t.Client.Invoices}
// [1] = {i => (i.InvoiceID == 5)}
// DebugView: ".Call System.Linq.Enumerable.Count(
// ($t.Client).ClientInvoices,
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>)
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>
// (SLApp.Web.ClientInvoice $i){ $i.ClientInvoiceID == 5 }"
Here's a working program that does what I think you'd like. It defines a function that takes a path to an integer property inside a collection, and an integer value. It then checks whether or not that collection has Count > 0 of that value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections;
namespace Test_Console
{
public class Subscription
{
public int Id { get; set; }
public Client Client { get; set; }
}
public class Client
{
public ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
var subscriptions = new[]
{
new Subscription { Id = 1, Client = new Client { Invoices = new [] {
new Invoice { Id = 1 },
new Invoice { Id = 2 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 2, Client = new Client { Invoices = new [] {
new Invoice { Id = 4 },
new Invoice { Id = 5 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 3, Client = new Client { Invoices = new Invoice[] {
} } },
};
var propertyPath = "Client.Invoices.Id";
Console.WriteLine("What Id would you like to check " + propertyPath + " for?");
var propertyValue = int.Parse(Console.ReadLine());
var whereNumberOne = makeWhere<Subscription>(propertyPath, propertyValue);
Console.WriteLine("The following Subscription objects match:");
foreach (var s in subscriptions.Where(whereNumberOne).ToList())
{
Console.WriteLine("Id: " + s.Id);
}
}
private static Func<T, bool> makeWhere<T>(string propertyPath, int propertyValue)
{
string[] navigateProperties = propertyPath.Split('.');
var currentType = typeof(T);
var functoidChain = new List<Func<object, object>>();
functoidChain.Add(x => x); // identity function starts the chain
foreach (var nextProperty in navigateProperties)
{
// must be inside loop so the closer on the functoids works properly
PropertyInfo nextPropertyInfo;
if (currentType.IsGenericType
&& currentType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
nextPropertyInfo = currentType.GetGenericArguments()[0].GetProperty(nextProperty);
functoidChain.Add(x =>
((IEnumerable<object>)x)
.Count(y => (int)nextPropertyInfo.GetValue(y, null) == propertyValue)
);
}
else
{
nextPropertyInfo = currentType.GetProperty(nextProperty);
functoidChain.Add(x => nextPropertyInfo.GetValue(x, null));
}
currentType = nextPropertyInfo.PropertyType;
}
// compose the functions together
var composedFunctoidChain = functoidChain.Aggregate((f, g) => x => g(f(x)));
var leftSide = new Func<T, int>(x => (int)composedFunctoidChain(x));
return new Func<T, bool>(r => leftSide(r) > 0);
}
}
}
I think this should get you closer to what you're going for:
static Expression<Func<T, bool>> CreateAnyExpression<T, T2>(string propertyPath,
Expression<Func<T2, bool>> matchExpression)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "s");
var propertyNames = propertyPath.Split('.');
Expression propBase = parameterExpression;
foreach(var propertyName in propertyNames)
{
PropertyInfo property = type.GetProperty(propertyName);
propBase = Expression.Property(propBase, property);
type = propBase.Type;
}
var itemType = type.GetGenericArguments()[0];
// .Any(...) is better than .Count(...) > 0
var anyMethod = typeof(Enumerable).GetMethods()
.Single(m => m.Name == "Any" && m.GetParameters().Length == 2)
.MakeGenericMethod(itemType);
var callToAny = Expression.Call(anyMethod, propBase, matchExpression);
return Expression.Lambda<Func<T, bool>>(callToAny, parameterExpression);
}
Calling it like this:
CreateAnyExpression<Subscription, Invoice>("Client.Invoices", i => i.InvoiceID == 1)
... yields the following Expression<Func<Subscription,bool>>:
s => s.Client.Invoices.Any(i => (i.InvoiceID == 1))
Here's a working program building Linq Expression
{(x.Children.Count(y => y.SomeID == SomeVar) > 0)}
using System;
using System.Linq;
using System.Linq.Expressions;
namespace ExpressionTree
{
class Program
{
static void Main(string[] args)
{
ParameterExpression foundX = Expression.Parameter(typeof(Parent), "x");
Guid[] guids = new Guid[1] { Guid.NewGuid() };
Expression expression = GetCountWithPredicateExpression(guids, foundX);
}
private static Expression GetCountWithPredicateExpression(Guid[] idsToFilter, ParameterExpression foundX)
{
System.Reflection.PropertyInfo childIDPropertyInfo = typeof(Child).GetProperty(nameof(Child.SomeID));
ParameterExpression foundY = Expression.Parameter(typeof(Child), "y");
Expression childIDLeft = Expression.Property(foundY, childIDPropertyInfo);
Expression conditionExpression = Expression.Constant(false, typeof(bool));
foreach (Guid id in idsToFilter)
conditionExpression = Expression.Or(conditionExpression, Expression.Equal(childIDLeft, Expression.Constant(id)));
Expression<Func<Child, bool>> idLambda = Expression.Lambda<Func<Child, bool>>(conditionExpression, foundY);
var countMethod = typeof(Enumerable).GetMethods()
.First(method => method.Name == "Count" && method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Child));
System.Reflection.PropertyInfo childrenPropertyInfo = typeof(Parent).GetProperty("Children");
Expression childrenLeft = Expression.Property(foundX, childrenPropertyInfo);
Expression ret = Expression.GreaterThan(Expression.Call(countMethod, childrenLeft, idLambda), Expression.Constant(0));
return ret;
}
}
public class Parent
{
public Child[] Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Guid SomeID { get; set; }
}
}

Categories

Resources