Dynamic LINQ aggregates on IQueryable as a single query - c#

I'm building a datagrid library that works on generic IQueryable data sources. At the bottom selected columns will have aggregates: sum, average, count etc.
I can compute the sum/average/count individually using the code from this article How to do a Sum using Dynamic LINQ
I don't want to run them individually for a datasource, as this would cause multiple queries on the database, I would rather create a single expression tree an execute this as a single query.
In static LINQ you'd do all the .Sum, .Average and .Count methods and return a new anonymous type with the values. I don't need an anonymous type (unless this is the only way): a list or array of the aggregates would be fine.
I assume from the other article I would need to string together a series of MethodCallExpression objects somehow. Can anyone help?

I found an alternative approach which uses the Dynamic LINQ library and avoids having to construct convoluted expression trees.
The solution is in the unit test below for anyone who is interested. I have a random dataset called TestQueryableDataset. The generic type of this IQueryable datasource has a Total property (decimal), a Discount property (nullable decimal) and an ID property (int).
The unit test gets the expected results first, using static LINQ queries.
It then constructs a select statement that uses the groupby variable 'it' to compute the sum, average and count. The property names are passed in by string to demonstrate this is stringly-typed.
The group-by method .GroupBy(x=> 1) is a dummy grouping to enable the aggregates to apply to the whole dataset.
Note that this returns a single dynamic result with properties t0, t1 and t2. However, the groupby/select operation still returns an IQueryable but with a single result. We have to use the t.Cast().First(); to convert to an array of object, then get the first result.
We can then use reflection to get the properties of each result (t0, t1, t2) as the actual values and assert that they match the static result we got earlier.
[TestMethod()]
[TestProperty("Anvil.DataSets", "QueryableExtensions")]
public void DynamicAggregate_test()
{
var source = new Anvil.Test.DataSets.TestQueryableDataset();
var data = source.GetData();
var expectedTotal = (from d in data select d.Total).Sum();
var expectedDiscount = (from d in data select d.Discount).Average();
var expectedCount = (from d in data select d.ID).Count();
const string prop0 = "Total";
const string prop1 = "Discount";
const string prop2 = "ID";
string sumExpr = string.Format("new ( Sum(it.{0}) as t0, Average(it.{1}) as t1 , Count() as t2)", prop0,prop1, prop2);
var t = data.GroupBy(x => 1).Select(sumExpr);
var firstItem = t.Cast<object>().First();
var ttype = firstItem.GetType();
var p0 = ttype.GetProperty("t0");
var p1 = ttype.GetProperty("t1");
var p2 = ttype.GetProperty("t2");
decimal actualTotal = (decimal)(p0.GetValue(firstItem));
decimal actualDiscount = (decimal)(p1.GetValue(firstItem));
int actualCount = (int)(p2.GetValue(firstItem));
Assert.AreEqual(expectedTotal, actualTotal);
Assert.AreEqual(expectedDiscount, actualDiscount);
Assert.AreEqual(expectedCount, actualCount);
}
See also:
Dynamic Linq GroupBy
System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)
http://developergems.blogspot.co.uk/2012/02/group-by-with-dynamic-linq.html

You don't need anonymous type. You just need a type with the 3 properties Sum, Count and Average. Sum and Average type aren't known at design time. So, use Object type for these 2 properties. Count is always an int.
public class Aggregation
{
public Aggregation(object sum, object average, int count)
{
Sum = sum;
Average = average;
Count = count;
}
public object Sum { get; private set; }
public object Average { get; private set; }
public int Count { get; private set; }
}
Like the Sum extension method described in the article How to do a Sum using Dynamic LINQ, you can write an Aggregate extension method which compute an Aggregation class instance from a IQueryable collection and a property name.
The real difficulty is about determining the Average overload method which match the property type. Overload can't be determined from the return type but from the return type of the lambda expression used as second argument.
For example, if the property type is an int, code has to select the public static double Average<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, int>> selector
) overload.
public static Aggregation Aggregate(this IQueryable source, string member)
{
if (source == null)
throw new ArgumentNullException("source");
if (member == null)
throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
// Methods
MethodInfo sumMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Sum"
&& m.ReturnType == property.PropertyType // should match the type of the property
&& m.IsGenericMethod);
MethodInfo averageMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Average"
&& m.IsGenericMethod
&& m.GetParameters()[1]
.ParameterType
.GetGenericArguments()[0]
.GetGenericArguments()[1] == property.PropertyType);
MethodInfo countMethod = typeof(Queryable).GetMethods().First(
m => m.Name == "Count"
&& m.IsGenericMethod);
return new Aggregation(
source.Provider.Execute(
Expression.Call(
null,
sumMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
source.Provider.Execute(
Expression.Call(
null,
averageMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) })),
(int)source.Provider.Execute(
Expression.Call(
null,
countMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression })));
}

here is my solution for sum, average and min, max .. this is what i have used in one of the projects.
public static object AggregateFunc(this IQueryable source, string function, string member)
{
if (source == null) throw new ArgumentNullException("source");
if (member == null) throw new ArgumentNullException("member");
// Properties
PropertyInfo property = source.ElementType.GetProperty(member);
ParameterExpression parameter = Expression.Parameter(source.ElementType, "s");
// We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
// which is expressed as ( (TSource s) => s.Price );
Type propertyType = property.PropertyType;
Type convertPropType = property.PropertyType;
if (function == "Sum")//convert int to bigint
{
if (propertyType == typeof(Int32))
convertPropType = typeof(Int64);
else if (propertyType == typeof(Int32?))
convertPropType = typeof(Int64?);
}
Expression selector = Expression.Lambda(Expression.Convert(Expression.MakeMemberAccess(parameter, property), convertPropType), parameter);
//var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function);
// Method
MethodInfo aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.IsGenericMethod
&& m.GetParameters().Length == 2 && m.GetParameters()[1].ParameterType.GenericTypeArguments[0].GenericTypeArguments[1] == convertPropType);// very hacky but works :)
MethodCallExpression callExpr;
// Sum, Average
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
// Min, Max
else
{
aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
m => m.Name == function
&& m.GetGenericArguments().Length == 2
&& m.IsGenericMethod);
if (aggregateMethod != null)
{
callExpr = Expression.Call(
null,
aggregateMethod.MakeGenericMethod(new[] { source.ElementType, propertyType }),
new[] { source.Expression, Expression.Quote(selector) });
return source.Provider.Execute(callExpr);
}
}
return null;
}

Related

How to build dynamic query with Where and OR using Expression

I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.

Dynamic expression tree with method 'Select'

I'm trying to build the following lambda expression using the expression tree ->
info => info.event_objects.Select(x => x.object_info.contact_info)
I researched a lot and find some answers on the StackOverflow.
This one helped me to build the
info =>
info.event_objects.Any(x => x.object_info.contact_info.someBool == true)
As you can see, the method 'Any' is easy to get.
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any"
&& m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
The main problem is with the method 'Select'. If you will try to change the Name "Any" to "Select", you will get the following exception:
var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name ==
"Select" && m.GetParameters().Length == 2);
selectMethod = selectMethod.MakeGenericMethod(childType);
Additional information: Sequence contains more than one matching element
Another way I've tried:
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name
== "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p =>
p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
It seems work, but then I get the exception here:
navigationPropertyPredicate = Expression.Call(selectMethod, parameter,
navigationPropertyPredicate);
Additional information: Method
System.Collections.Generic.IEnumerable`1[TResult] Select[TSource,TResult]
(System.Collections.Generic.IEnumerable`1[TSource],
System.Func`2[TSource,TResult]) is a generic method definition>
After that, I've tried to use:
selectMethod = selectMethod.MakeGenericMethod(typeof(event_objects),
typeof(contact_info));
In fact, it doesn't help.
Here is my full code
public static Expression GetNavigationPropertyExpression(Expression parameter, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, navigationPropertyPredicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, "x");
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, innerProperties);
if (isCollection)
{
//var selectMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Select" && m.GetParameters().Length == 2);
//selectMethod = selectMethod.MakeGenericMethod(childType);
MethodInfo selectMethod = null;
foreach (MethodInfo m in typeof(Enumerable).GetMethods().Where(m => m.Name == "Select"))
foreach (ParameterInfo p in m.GetParameters().Where(p => p.Name.Equals("selector")))
if (p.ParameterType.GetGenericArguments().Count() == 2)
selectMethod = (MethodInfo)p.Member;
navigationPropertyPredicate = Expression.Call(selectMethod, parameter, navigationPropertyPredicate);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
else
{
resultExpression = navigationPropertyPredicate;
}
}
else
{
var childProperty = parameter.Type.GetProperty(properties[0]);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(true, typeof(bool));
navigationPropertyPredicate = Expression.Lambda(left);
resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
}
return resultExpression;
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
[TestMethod]
public void TestDynamicExpression()
{
var parameter = Expression.Parameter(typeof(event_info), "x");
var expression = GetNavigationPropertyExpression(parameter, "event_objects", "object_info", "contact_info");
}
Edit: unfortunately, I've tried answers from this question, but it doesn't seem work
You can avoid finding the correct generic method overload via reflection (which is complicated and error prone as you already noticed) by using one of the two Expression.Call method overloads (one for static and one for instance methods) accepting string methodName and Type[] typeArguments.
Also the current implementation is overcomplicated and contains other problems, due to the lack of clear separation of expression and lambda expression building.
Here is a correct working implementation:
public static LambdaExpression GetNavigationPropertySelector(Type type, params string[] properties)
{
return GetNavigationPropertySelector(type, properties, 0);
}
private static LambdaExpression GetNavigationPropertySelector(Type type, string[] properties, int depth)
{
var parameter = Expression.Parameter(type, depth == 0 ? "x" : "x" + depth);
var body = GetNavigationPropertyExpression(parameter, properties, depth);
return Expression.Lambda(body, parameter);
}
private static Expression GetNavigationPropertyExpression(Expression source, string[] properties, int depth)
{
if (depth >= properties.Length)
return source;
var property = Expression.Property(source, properties[depth]);
if (typeof(IEnumerable).IsAssignableFrom(property.Type))
{
var elementType = property.Type.GetGenericArguments()[0];
var elementSelector = GetNavigationPropertySelector(elementType, properties, depth + 1);
return Expression.Call(
typeof(Enumerable), "Select", new Type[] { elementType, elementSelector.Body.Type },
property, elementSelector);
}
else
{
return GetNavigationPropertyExpression(property, properties, depth + 1);
}
}
The first is the public method. It internally uses the next two private methods to recursively build the desired lambda. As you can see, I distinguish between building lambda expression and just expression to be used as lambda body.
Test:
var selector = GetNavigationPropertySelector(typeof(event_info),
"event_objects", "object_info", "contact_info");
Result:
x => x.event_objects.Select(x1 => x1.object_info.contact_info)
"Additional information: Sequence contains more than one matching element"
Unlike "Any()", for "Select()" there are two overloads with two parameters:
Select<TS, TR>(IE<TS> source, Func<TS, TR> selector)
Select<TS, TR>(IE<TS> source, Func<TS, int, TR> selector)
(takes the "(item, index) => " selector lambda)
Since your code already relies on "esoteric knowledge" anyway, just take the first one of them:
var selectMethod = typeof(Enumerable).GetMethods()
.First(m => m.Name == nameof(Enumerable.Select)
&& m.GetParameters().Length == 2);

OrderBy with variable TKey

I have a list of FileInfo which I have to sort by different properties, e.g.
List<FileInfo> infoListOrdered = infoList.OrderBy(x => x.CreationTime).ToList();
Instead of writing the expression for every FileInfo property, would it be possible to pass the TKey as a parameter?
No, TKey is a type; the lamba x=>x.CreationTime is the value selector returning a value of type TKey. You can't select something based on its type only, for one there may be multiple property instances of that type in your class.
Here's a solution based on my (mis)understanding of the question. It gives OrderBy for each property prop that you might want to order by:
static void Main()
{
IEnumerable<FileInfo> infoList = XXX; // your source to sort
var orderByMeth = typeof(Enumerable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var tFileInfo = typeof(FileInfo);
foreach (var prop in tFileInfo.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance))
{
var tKey = prop.PropertyType;
var xParam = Expression.Parameter(tFileInfo);
var propBody = Expression.Property(xParam, prop.GetMethod);
var lambda = Expression.Lambda(propBody, xParam);
var func = lambda.Compile();
var orderByMethConstr = orderByMeth.MakeGenericMethod(tFileInfo, tKey);
var result = orderByMethConstr.Invoke(null, new object[] { infoList, func, });
var infoListOrdered = (IOrderedEnumerable<FileInfo>)result;
// keep infoListOrdered; foreach through it to get that particular ordering
}
}

C# Linq OrderBy filtering null or empty values to be last

I try to make my custom orderby extension method, i successfully worked my code but in addition i want to list null or empty or zero values last in result, anyone can help me about that issue ?
Here is my extension method to orderby
public static IQueryable<T> OrderBy<T>(this IQueryable<T> q, string SortField, bool isAsc)
{
//var nullExpr = Expression.Constant(null, typeof(T));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = isAsc ? "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);
}
Thanks in advance
The simplest way is to use
OrderBy(e => String.IsNullOrEmpty(e.TeamName)
This doesn't require any extension method or custom IComparer implementation etc.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => String.IsNullOrEmpty(e.TeamName))
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
Without using an extension method....
Create a custom IComparer<string> to check the empty values before using the default String.Compare. The first checks will return -1 instead of 1 or 1 instead of -1, if using the standard string comparison.
/// <summary>
/// Returns -1 instead of 1 if y is IsNullOrEmpty when x is Not.
/// </summary>
public class EmptyStringsAreLast : IComparer<string>
{
public int Compare(string x, string y)
{
if (String.IsNullOrEmpty(y) && !String.IsNullOrEmpty(x))
{
return -1;
}
else if (!String.IsNullOrEmpty(y) && String.IsNullOrEmpty(x))
{
return 1;
}
else
{
return String.Compare(x, y);
}
}
}
Pass your EmptyStringsAreLast comparer into the OrderBy of Lambda expression. In this solution teams who have entered the race should appear alphabetical order, but the unaffiliated race entries should appear at then end.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => e.TeamName, new EmptyStringsAreLast())
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
This answer is perhaps what you were originally looking for - using your generic extension method:
public static IQueryable<T> OrderByFieldNullsLast<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
//We are rebuilding .OrderByDescending(p => p.SortField.HasValue).ThenBy(p => p.SortField)
//i.e. sort first by whether sortfield has a value, then by sortfield asc or sortfield desc
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, SortField);
var hasValue = Expression.Property(prop, "HasValue");
var exp = Expression.Lambda(hasValue, param);
string method = "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { q.ElementType, exp.Body.Type };
method = Ascending ? "ThenBy" : "ThenByDescending";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types,orderByCallExpression, exp);
return q.Provider.CreateQuery<T>(ThenByCallExpression);
}
Building on Dave Anson's answer, you can user Comparer.Create() to create the Comparer from a lambda. Here's an example that sorts unsorted by its myString string fields, with null or empty strings appearing last.
var sorted = unsorted.OrderBy(x => x.myString, Comparer<string>.Create((x, y) => {
if ( string.IsNullOrEmpty(y) && !string.IsNullOrEmpty(x)) return -1;
else if (!string.IsNullOrEmpty(y) && string.IsNullOrEmpty(x)) return +1;
else return string.Compare(x, y);
}))
(To put them first, switch the signs on the 1 constants)
it works for me:
private static IQueryable<T> GetOrderQuery<T>(this IQueryable<T> q, BaseFilterCollection filter)
{
q = q.OrderBy(GetExpression<T>(filter.SortField));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, filter.SortField);
var exp = Expression.Lambda(prop, param);
string method = filter.SortDirection == SortDirectionType.Asc ? "ThenBy" : "ThenByDescending";
Type[] types = { q.ElementType, exp.Body.Type };
var rs = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(rs);
}
private static Expression<Func<T, bool>> GetExpression<T>(string sortField)
{
ParameterExpression param = Expression.Parameter(typeof(T), "p");
Expression prop = Expression.Property(param, sortField);
var info = typeof(T).GetProperty(sortField, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Expression exp = Expression.Equal(prop, info.PropertyType.IsValueType
? Expression.Constant(Activator.CreateInstance(info.PropertyType))
: Expression.Constant(null));
return Expression.Lambda<Func<T, bool>>(exp, param);
}
You dont need to complicate, the easiest way is to do something like this:
YourList.OrderByDescending(x => string.IsNullOrEmpty(x.value)
Use OrderByDescending or OrderBy depending on if you want to see empty strings in the beginning or last.
Regards

Dynamic predicates for Linq-to-Entity queries

The following Linq-to-Entities query works fine:
var query = repository.Where(r => r.YearProp1.HasValue &&
r.YearProp1 >= minYear &&
r.YearProp1 <= maxYear);
My database has a dozen or so columns that all report year-related information (short? data type). I want to reuse the same Linq-to-Entities logic for all these columns. Something like:
Func<RepoEntity, short?> fx = GetYearPropertyFunction();
var query = repository.Where(r => fx(r).HasValue &&
fx(r) >= minYear &&
fx(r) <= maxYear);
This results in the error:
LINQ to Entities does not recognize the method
'System.Nullable`1[System.Int16] fx(RepoEntity)' method, and this
method cannot be translated into a store expression.
I understand why I am getting the error, but am wondering if there is a workaround that doesn't involve duplicating code a dozen times just to change the property on which the SQL query is operating.
I would be reusing the function in more than one query, so I guess the general version of my question is: Is there a way to convert a simple property-getter lambda function to an Expression that can be consumed by Linq-to-Entities?
Building off of Raphaël Althaus' answer, but adding the generic selector you were originally looking for:
public static class Examples
{
public static Expression<Func<MyEntity, short?>> SelectPropertyOne()
{
return x => x.PropertyOne;
}
public static Expression<Func<MyEntity, short?>> SelectPropertyTwo()
{
return x => x.PropertyTwo;
}
public static Expression<Func<TEntity, bool>> BetweenNullable<TEntity, TNull>(Expression<Func<TEntity, Nullable<TNull>>> selector, Nullable<TNull> minRange, Nullable<TNull> maxRange) where TNull : struct
{
var param = Expression.Parameter(typeof(TEntity), "entity");
var member = Expression.Invoke(selector, param);
Expression hasValue = Expression.Property(member, "HasValue");
Expression greaterThanMinRange = Expression.GreaterThanOrEqual(member,
Expression.Convert(Expression.Constant(minRange), typeof(Nullable<TNull>)));
Expression lessThanMaxRange = Expression.LessThanOrEqual(member,
Expression.Convert(Expression.Constant(maxRange), typeof(Nullable<TNull>)));
Expression body = Expression.AndAlso(hasValue,
Expression.AndAlso(greaterThanMinRange, lessThanMaxRange));
return Expression.Lambda<Func<TEntity, bool>>(body, param);
}
}
Could be used somewhat like the original query you were looking for:
Expression<Func<MyEntity, short?>> whatToSelect = Examples.SelectPropertyOne;
var query = Context
.MyEntities
.Where(Examples.BetweenNullable<MyEntity, short>(whatToSelect, 0, 30));
A predicate is a filter in itself that should evaluate to bool (for whether or not to include it in the results). You can rework your method to look like this and it should work:
public static Expression<Func<RepoEntity, bool>> FitsWithinRange(int minYear, int maxYear)
{
return w => w.HasValue && w >= minYear && w <= maxYear;
}
Edit: Oh and to use it:
var query = repository.Where(Repository.FitsWithinRange(minYear, maxYear));
You could do something like that (not sure if it will work "as is" in linq2 entities, but if you have a problem... just tell)
usage
var query = <your IQueryable<T> entity>.NullableShortBetween(1, 3).ToList();
function
public static IQueryable<T> NullableShortBetween<T>(this IQueryable<T> queryable, short? minValue, short? maxValue) where T: class
{
//item (= left part of the lambda)
var parameterExpression = Expression.Parameter(typeof (T), "item");
//retrieve all nullable short properties of your entity, to change if you have other criterias to get these "year" properties
var shortProperties = typeof (T).GetProperties().Where(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
foreach (var shortProperty in shortProperties)
{
//item (right part of the lambda)
Expression memberExpression = parameterExpression;
//item.<PropertyName>
memberExpression = Expression.Property(memberExpression, shortProperty);
//item.<PropertyName>.HasValue
Expression firstPart = Expression.Property(memberExpression, "HasValue");
//item.<PropertyName> >= minValue
Expression secondPart = Expression.GreaterThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(minValue), typeof (short?)));
//item.<PropertyName> <= maxValue
var thirdPart = Expression.LessThanOrEqual(memberExpression, Expression.Convert(Expression.Constant(maxValue), typeof (short?)));
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue
var result = Expression.And(firstPart, secondPart);
//item.<PropertyName>.HasValue && item.<PropertyName> >= minValue && item.<PropertyName> <= maxValue
result = Expression.AndAlso(result, thirdPart);
//pass the predicate to the queryable
queryable = queryable.Where(Expression.Lambda<Func<T, bool>>(result, new[] {parameterExpression}));
}
return queryable;
}
EDIT : another solution, based on "simple" reflection, which "looks" as the one you want
public static short? GetYearValue<T>(this T instance)
{
var propertyInfo = typeof(T).GetProperties().FirstOrDefault(m => m.CanRead && m.CanWrite && m.PropertyType == typeof(short?));
return propertyInfo.GetValue(instance, null) as short?;
}
usage
var result = list.Where(item => item.GetYearValue() != null && item.GetYearValue() >= 1 && item.GetYearValue() <= 3).ToList();

Categories

Resources