I have the following List with OrderBy parameters:
List<String> fields = new List<String> { "+created", "-approved", "+author" }
This would result in the following Linq query:
IQueryable<Post> posts = _context.posts.AsQueryable();
posts = posts
.OrderBy(x => x.Created)
.ThenByDescending(x => x.Approved);
.ThenBy(x => x.Author.Name);
So basically the rules are:
Use the first item in the OrderBy and the others in ThenBy.
Use descending when the field starts with - and ascending when with +.
My idea would be to have something like:
OrderExpression expression = posts
.Add(x => x.Created, "created")
.Add(x => x.Approved, "approved")
.Add(x => x.Author.Name, "author");
So the expression associates the post properties / child properties to each key in fields. Then it would be applied as follows:
posts = posts.OrderBy(expression, fields);
So the OrderBy extension would go through each item in the OrderExpression and apply the rules (1) and (2) to build the query:
posts = posts
.OrderBy(x => x.Created)
.ThenByDescending(x => x.Approved);
.ThenBy(x => x.Author.Name);
How can this be done?
This following class will help you do it. You can find the explanation of code inline.
public static class MyClass
{
public static IQueryable<T> Order<T>(
IQueryable<T> queryable,
List<string> fields,
//We pass LambdaExpression because the selector property type can be anything
Dictionary<string, LambdaExpression> expressions)
{
//Start with input queryable
IQueryable<T> result = queryable;
//Loop through fields
for (int i = 0; i < fields.Count; i++)
{
bool ascending = fields[i][0] == '+';
string field = fields[i].Substring(1);
LambdaExpression expression = expressions[field];
MethodInfo method = null;
//Based on sort order and field index, determine which method to invoke
if (ascending && i == 0)
method = OrderbyMethod;
else if (ascending && i > 0)
method = ThenByMethod;
else if (!ascending && i == 0)
method = OrderbyDescendingMethod;
else
method = ThenByDescendingMethod;
//Invoke appropriate method
result = InvokeQueryableMethod( method, result, expression);
}
return result;
}
//This method can invoke OrderBy or the other methods without
//getting as input the expression return value type
private static IQueryable<T> InvokeQueryableMethod<T>(
MethodInfo methodinfo,
IQueryable<T> queryable,
LambdaExpression expression)
{
var generic_order_by =
methodinfo.MakeGenericMethod(
typeof(T),
expression.ReturnType);
return (IQueryable<T>)generic_order_by.Invoke(
null,
new object[] { queryable, expression });
}
private static readonly MethodInfo OrderbyMethod;
private static readonly MethodInfo OrderbyDescendingMethod;
private static readonly MethodInfo ThenByMethod;
private static readonly MethodInfo ThenByDescendingMethod;
//Here we use reflection to get references to the open generic methods for
//the 4 Queryable methods that we need
static MyClass()
{
OrderbyMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "OrderBy" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) }));
OrderbyDescendingMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "OrderByDescending" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) }));
ThenByMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "ThenBy" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) }));
ThenByDescendingMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "ThenByDescending" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) }));
}
}
Here is an example usage:
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name + ", " + Age;
}
}
class Program
{
static void Main(string[] args)
{
List<Person> persons = new List<Person>
{
new Person {Name = "yacoub", Age = 30},
new Person {Name = "yacoub", Age = 32},
new Person {Name = "adam", Age = 30},
new Person {Name = "adam", Age = 33},
};
var query = MyClass.Order(
persons.AsQueryable(),
new List<string> { "+Name", "-Age" },
new Dictionary<string, LambdaExpression>
{
{"Name", (Expression<Func<Person, string>>) (x => x.Name)},
{"Age", (Expression<Func<Person, int>>) (x => x.Age)}
});
var result = query.ToList();
}
}
This answer is the joint effort of #YacoubMassad and me. Take a look at the separate answers for details. The following code works perfectly and even translates to SQL without issues (I checked the query with the answer to this question on a 2008 R2), so all the sorting is done on the server (or wherever you data is, it works with simple lists too of course).
Example usage:
OrderExpression<Post> expression = new OrderExpression<Post>()
.Add(x => x.Created, "created")
.Add(x => x.Approved, "approved")
.Add(x => x.Author.Name, "author");
IQueryable<Post> posts = _context.posts.AsQueryable();
posts = posts.OrderBy(expression, "+created", "-approved", "+author");
// OR
posts = posts.OrderBy(expression, new string[]{"+created", "-approved", "+author"});
// OR
posts = posts.OrderBy(expression, fields.ToArray[]);
And of course a live demo on dotNetFiddle
Code:
public class OrderExpressions<T>
{
private readonly Dictionary<string, LambdaExpression> _mappings =
new Dictionary<string, LambdaExpression>();
public OrderExpressions<T> Add<TKey>(Expression<Func<T, TKey>> expression, string keyword)
{
_mappings.Add(keyword, expression);
return this;
}
public LambdaExpression this[string keyword]
{
get { return _mappings[keyword]; }
}
}
public static class KeywordSearchExtender
{
private static readonly MethodInfo OrderbyMethod;
private static readonly MethodInfo OrderbyDescendingMethod;
private static readonly MethodInfo ThenByMethod;
private static readonly MethodInfo ThenByDescendingMethod;
//Here we use reflection to get references to the open generic methods for
//the 4 Queryable methods that we need
static KeywordSearchExtender()
{
OrderbyMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "OrderBy" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) }));
OrderbyDescendingMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "OrderByDescending" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IQueryable<>), typeof(Expression<>) }));
ThenByMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "ThenBy" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) }));
ThenByDescendingMethod = typeof(Queryable)
.GetMethods()
.First(x => x.Name == "ThenByDescending" &&
x.GetParameters()
.Select(y => y.ParameterType.GetGenericTypeDefinition())
.SequenceEqual(new[] { typeof(IOrderedQueryable<>), typeof(Expression<>) }));
}
//This method can invoke OrderBy or the other methods without
//getting as input the expression return value type
private static IQueryable<T> InvokeQueryableMethod<T>(
MethodInfo methodinfo,
IQueryable<T> queryable,
LambdaExpression expression)
{
var generic_order_by =
methodinfo.MakeGenericMethod(
typeof(T),
expression.ReturnType);
return (IQueryable<T>)generic_order_by.Invoke(
null,
new object[] { queryable, expression });
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> data,
OrderExpressions<T> mapper, params string[] arguments)
{
if (arguments.Length == 0)
throw new ArgumentException(#"You need at least one argument!", "arguments");
List<SortArgument> sorting = arguments.Select(a => new SortArgument(a)).ToList();
IQueryable<T> result = null;
for (int i = 0; i < sorting.Count; i++)
{
SortArgument sort = sorting[i];
LambdaExpression lambda = mapper[sort.Keyword];
if (i == 0)
result = InvokeQueryableMethod(sort.Ascending ?
OrderbyMethod : OrderbyDescendingMethod, data, lambda);
else
result = InvokeQueryableMethod(sort.Ascending ?
ThenByMethod : ThenByDescendingMethod, result, lambda);
}
return result;
}
}
public class SortArgument
{
public SortArgument()
{ }
public SortArgument(string term)
{
if (term.StartsWith("-"))
{
Ascending = false;
Keyword = term.Substring(1);
}
else if (term.StartsWith("+"))
{
Ascending = true;
Keyword = term.Substring(1);
}
else
{
Ascending = true;
Keyword = term;
}
}
public string Keyword { get; set; }
public bool Ascending { get; set; }
}
Edit: Changed Code to closely match your syntax
This code sorts on the client, but works with all IEnumerables. If you absolutely need to sort on the database, take a look at Yacoub's static MyClass() to see how he solved this problem.
The example below is based on the information you provided, you might need to adjust it a bit.
public class DemoClass
{
public DateTime Created { get; set; }
public bool Approved { get; set; }
public Person Author { get; set; }
}
public class Person
{
public string Name { get; set; }
}
Since your example contains author which actually resolves to Author.Name, you need to create some sort of mapping for your keywords (Like you did with your OrderExpression class).
public class OrderExpressions<T>
{
private readonly Dictionary<string,Func<T,object>> _mappings =
new Dictionary<string,Func<T, object>>();
public OrderExpressions<T> Add(Func<T, object> expression, string keyword)
{
_mappings.Add(keyword, expression);
return this;
}
public Func<T, object> this[string keyword]
{
get { return _mappings[keyword]; }
}
}
Which could be used like this:
OrderExpressions<DemoClass> expressions = new OrderExpressions<DemoClass>()
.Add(x => x.Created, "created")
.Add(x => x.Approved, "approved")
.Add(x => x.Author.Name, "author");
You can pass those functions / lambda expressions, directly to Linq and add the next comparison one after another. Start with OrderBy or OrderByDescrending, that will give you your first IOrderedEnumerable and then add all remaining arguments with ThenBy or ThenByDescending.
public static class KeywordSearchExtender
{
public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> data,
OrderExpressions<T> mapper, params string[] arguments)
{
if (arguments.Length == 0)
throw new ArgumentException(#"You need at least one argument!", "arguments");
List<SortArgument> sorting = arguments.Select(a => new SortArgument(a)).ToList();
IOrderedEnumerable<T> result = null;
for (int i = 0; i < sorting.Count; i++)
{
SortArgument sort = sorting[i];
Func<T, object> lambda = mapper[sort.Keyword];
if (i == 0)
result = sorting[i].Ascending ?
data.OrderBy(lambda) :
data.OrderByDescending(lambda);
else
result = sorting[i].Ascending ?
result.ThenBy(lambda) :
result.ThenByDescending(lambda);
}
return result;
}
}
public class SortArgument
{
public SortArgument()
{ }
public SortArgument(string term)
{
if (term.StartsWith("-"))
{
Ascending = false;
Keyword = term.Substring(1);
}
else if (term.StartsWith("+"))
{
Ascending = true;
Keyword = term.Substring(1);
}
else
{
Ascending = true;
Keyword = term;
}
}
public string Keyword { get; set; }
public bool Ascending { get; set; }
}
All together it be used like this:
var data = WhateverYouDoToGetYourData();
var expressions = new OrderExpressions<DemoClass>()
.Add(x => x.Created, "created")
.Add(x => x.Approved, "approved")
.Add(x =>x.Author.Name, "author");
var result = data.OrderBy(expressions, "+created", "-approved", "+author");
// OR
var result = data.OrderBy(expressions, fields);
You can find my proof-of-concept on dotNetFiddle.
Related
I am using this snippet below for Ordering my Linq queries dynamically and works great. I am not great at reflection or complex linq queries but I need a way that when ascending order is used, that NULL values are last and vice versa.
So if my property name was an integer and the column values were 1, 3, 5, all NULL rows would be at the end, not at the beginning by default. What can I add to this expression to make that happen?
This code works with entity framework and still needs to for the NULL comparison.
Example
list.OrderBy("NAME DESC").ToList()
Class
public static class OrderByHelper
{
public static IOrderedQueryable<T> ThenBy<T>(this IEnumerable<T> enumerable, string orderBy)
{
return enumerable.AsQueryable().ThenBy(orderBy);
}
public static IOrderedQueryable<T> ThenBy<T>(this IQueryable<T> collection, string orderBy)
{
if (string.IsNullOrWhiteSpace(orderBy))
orderBy = "ID DESC";
IOrderedQueryable<T> orderedQueryable = null;
foreach (OrderByInfo orderByInfo in ParseOrderBy(orderBy, false))
orderedQueryable = ApplyOrderBy<T>(collection, orderByInfo);
return orderedQueryable;
}
public static IOrderedQueryable<T> OrderBy<T>(this IEnumerable<T> enumerable, string orderBy)
{
return enumerable.AsQueryable().OrderBy(orderBy);
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> collection, string orderBy)
{
if (string.IsNullOrWhiteSpace(orderBy))
orderBy = "ID DESC";
IOrderedQueryable<T> orderedQueryable = null;
foreach (OrderByInfo orderByInfo in ParseOrderBy(orderBy, true))
orderedQueryable = ApplyOrderBy<T>(collection, orderByInfo);
return orderedQueryable;
}
private static IOrderedQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo)
{
string[] props = orderByInfo.PropertyName.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
string methodName = String.Empty;
if (!orderByInfo.Initial && collection is IOrderedQueryable<T>)
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "ThenBy";
else
methodName = "ThenByDescending";
}
else
{
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "OrderBy";
else
methodName = "OrderByDescending";
}
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { collection, lambda });
}
private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy, bool initial)
{
if (String.IsNullOrEmpty(orderBy))
yield break;
string[] items = orderBy.Split(',');
foreach (string item in items)
{
string[] pair = item.Trim().Split(' ');
if (pair.Length > 2)
throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
string prop = pair[0].Trim();
if (String.IsNullOrEmpty(prop))
throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
SortDirection dir = SortDirection.Ascending;
if (pair.Length == 2)
dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
initial = false;
}
}
private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}
private enum SortDirection
{
Ascending = 0,
Descending = 1
}
It's relatively simple. For each passed sort selector, the method executes one of the following:
.OrderBy(x => x.Member)
.ThenBy(x => x.Member)
.OrderByDescending(x => x.Member)
.ThenByDescendiong(x => x.Member)
When the x.Member type is reference type or nullable value type, the desired behavior can be achieved by pre ordering with the same direction by the following expression
x => x.Member == null ? 1 : 0
Some people use ordering by bool, but I prefer to be explicit and use conditional operator with specific integer values. So the corresponding calls for the above calls would be:
.OrderBy(x => x.Member == null ? 1 : 0).ThenBy(x => x.Member)
.ThenBy(x => x.Member == null ? 1 : 0).ThenBy(x => x.Member)
.OrderByDescending(x => x.Member == null ? 1 : 0).ThenByDescending(x => x.Member)
.ThenByDescending(x => x.Member == null ? 1 : 0).ThenByDescending(x => x.Member)
i.e. the original method on the pre order expression followed by the ThenBy(Descending) with the original expression.
Here is the implementation:
public static class OrderByHelper
{
public static IOrderedQueryable<T> ThenBy<T>(this IEnumerable<T> source, string orderBy)
{
return source.AsQueryable().ThenBy(orderBy);
}
public static IOrderedQueryable<T> ThenBy<T>(this IQueryable<T> source, string orderBy)
{
return OrderBy(source, orderBy, false);
}
public static IOrderedQueryable<T> OrderBy<T>(this IEnumerable<T> source, string orderBy)
{
return source.AsQueryable().OrderBy(orderBy);
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderBy)
{
return OrderBy(source, orderBy, true);
}
private static IOrderedQueryable<T> OrderBy<T>(IQueryable<T> source, string orderBy, bool initial)
{
if (string.IsNullOrWhiteSpace(orderBy))
orderBy = "ID DESC";
var parameter = Expression.Parameter(typeof(T), "x");
var expression = source.Expression;
foreach (var item in ParseOrderBy(orderBy, initial))
{
var order = item.PropertyName.Split('.')
.Aggregate((Expression)parameter, Expression.PropertyOrField);
if (!order.Type.IsValueType || Nullable.GetUnderlyingType(order.Type) != null)
{
var preOrder = Expression.Condition(
Expression.Equal(order, Expression.Constant(null, order.Type)),
Expression.Constant(1), Expression.Constant(0));
expression = CallOrderBy(expression, Expression.Lambda(preOrder, parameter), item.Direction, initial);
initial = false;
}
expression = CallOrderBy(expression, Expression.Lambda(order, parameter), item.Direction, initial);
initial = false;
}
return (IOrderedQueryable<T>)source.Provider.CreateQuery(expression);
}
private static Expression CallOrderBy(Expression source, LambdaExpression selector, SortDirection direction, bool initial)
{
return Expression.Call(
typeof(Queryable), GetMethodName(direction, initial),
new Type[] { selector.Parameters[0].Type, selector.Body.Type },
source, Expression.Quote(selector));
}
private static string GetMethodName(SortDirection direction, bool initial)
{
return direction == SortDirection.Ascending ?
(initial ? "OrderBy" : "ThenBy") :
(initial ? "OrderByDescending" : "ThenByDescending");
}
private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy, bool initial)
{
if (String.IsNullOrEmpty(orderBy))
yield break;
string[] items = orderBy.Split(',');
foreach (string item in items)
{
string[] pair = item.Trim().Split(' ');
if (pair.Length > 2)
throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
string prop = pair[0].Trim();
if (String.IsNullOrEmpty(prop))
throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
SortDirection dir = SortDirection.Ascending;
if (pair.Length == 2)
dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
initial = false;
}
}
private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}
private enum SortDirection
{
Ascending = 0,
Descending = 1
}
}
One approach is to pass an additional expression for testing for null into the method, and use it in an additional OrderBy/ThenBy clause.
Two OrderBy clauses would be produced - the first one will be on nullOrder, while the second one will be on the actual property.
private static IOrderedQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo, Expression<Func<T,int>> nullOrder) {
...
if (!orderByInfo.Initial && collection is IOrderedQueryable<T>) {
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "ThenBy";
else
methodName = "ThenByDescending";
} else {
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "OrderBy";
else
methodName = "OrderByDescending";
}
if (nullOrder != null) {
collection = (IQueryable<T>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { collection, nullOrder });
// We've inserted the initial order by on nullOrder,
// so OrderBy on the property becomes a "ThenBy"
if (orderByInfo.Direction == SortDirection.Ascending)
methodName = "ThenBy";
else
methodName = "ThenByDescending";
}
// The rest of the method remains the same
return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { collection, lambda });
}
The caller would need to pass a null checker explicitly. Passing null for non-nullable fields should work. You can construct them once, and pass as needed:
static readonly Expression<Func<string,int>> NullStringOrder = s => s == null ? 1 : 0;
static readonly Expression<Func<int?,int>> NullIntOrder = i => !i.HasValue ? 1 : 0;
static readonly Expression<Func<long?,int>> NullLongOrder = i => !i.HasValue ? 1 : 0;
My approach is to create a generic class that implements IComparer<TClass>. This way you can use your class in all LINQ statements with a non-default comparer. The advantage is that you will have full type checking at compile time. You can't name properties that can't be compared or that can't be null
class NullValueLastComparer<TClass, TKey> : IComparer<TClass>
where TClass : class
where TKey : IComparable<TKey>
{
This generic class has two Type parameters: the class that you want to compare, and the type of the property you want to compare with. The where clauses assert that TClass is a reference type, so you can access Properties, and TKey is something that implements normal comparison.
To create objects for the class we have two Factory functions. Both functions need a KeySelector, similar to lots of Key Selectors you can find in LINQ. The KeySelector function is the function that will tell you which property must be used in your comparisons. It is similar to the KeySelector in function Enumerable.Where.
The second Create function gives you the possibility to provide a non-default comparer, again similar to a lot of functions in the Enumerable class:
public static IComparer<TClass> Create(Func<TClass, TKey> keySelector)
{ // call the other Create function, with the default TKey comparer
return Create(keySelector, Comparer<TKey>.Default);
}
public static IComparer<TClass> Create(Func<TClass, TKey> keySelector, IComparer<TKey> comparer)
{ // construct a null value last comparer object
// initialize with the key selector and the key comparer
return new NullValueLastComparer<TClass, TKey>()
{
KeySelector = keySelector,
KeyComparer = comparer,
};
}
I use a private constructor. Only the static create classes can construct the null value last comparer
private NullValueLastComparer() { }
Two properties: the key selector and the comparer:
private Func<TClass, TKey> KeySelector { get; set; }
private IComparer<TKey> KeyComparer { get; set; }
The actual compare function. It will use the KeySelector to get the values
that must be compared, and compares them such that a null value will be last.
public int Compare(TClass x, TClass y)
{
if (Object.ReferenceEquals(x, null))
throw new ArgumentNullException(nameof(x));
if (Object.ReferenceEquals(y, null)
throw new ArgumentNullException(nameof(y));
// get the values to compare
TKey keyX = KeySelector(x);
TKey keyY = KeySelector(y);
return this.Compare(keyX, keyY);
}
The private function that compares the Keys such that null values will be last
private int Compare(TKey x, TKey y)
{ // compare such that null values last, or if both not null, use IComparable
if (Object.ReferenceEquals(x, null))
{
if (Object.ReferenceEquals(y, null))
{ // both null
return 0;
}
else
{ // x null, y not null => x follows y
return +1;
}
}
else
{ // x not null
if (Object.ReferenceEquals(y, null))
{ // x not null; y null: x precedes y
return -1;
}
else
{
return this.KeyComparer.Compare(x, y);
}
}
}
}
Usage:
class Person
{
public string FirstName {get; set;}
public string FamilyName {get; set;}
}
// create a comparer that will put Persons without firstName last:
IComparer<Person> myComparer =
NullValueLastComparer<Person, string>.Create(person => person.FirstName);
Person person1 = ...;
Person person2 = ...;
int compareResult = myComparer.Compare(person1, person2);
This compare will compare Persons. When two Persons are compared, it will take person.FirstName for both persons, and will put the one without FirstName as last.
Usage in a complicated LINQ statement.
Note that there is full type checking at compile time.
IEnumerable<Person> myPersonCollection = ...
var sortedPersons = myPersonCollection
.OrderBy(person => person, myComparer)
.ThenBy(person => person.LastName)
.Select(person => ...)
.ToDictonary(...)
For dynamically constructed Order By expression like this list.OrderBy("NAME DESC").ToList(), you can use the following query helper extension method.
Usage
First of all, we check to make sure property name exists in the given Class. If we do not check, it'll throw run-time exception.
Then we use use either OrderByProperty or OrderByPropertyDescending.
string orderBy = "Name";
if (QueryHelper.PropertyExists<User>(orderBy))
{
list = list.OrderByProperty(orderBy);
- OR -
list = list.OrderByPropertyDescending(orderBy);
}
Here is the real world usage in my project at GitHub.
Query Helper
public static class QueryHelper
{
private static readonly MethodInfo OrderByMethod =
typeof (Queryable).GetMethods().Single(method =>
method.Name == "OrderBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo OrderByDescendingMethod =
typeof (Queryable).GetMethods().Single(method =>
method.Name == "OrderByDescending" && method.GetParameters().Length == 2);
public static bool PropertyExists<T>(string propertyName)
{
return typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) != null;
}
public static IQueryable<T> OrderByProperty<T>(
this IQueryable<T> source, string propertyName)
{
if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) == null)
{
return null;
}
ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
MethodInfo genericMethod =
OrderByMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) ret;
}
public static IQueryable<T> OrderByPropertyDescending<T>(
this IQueryable<T> source, string propertyName)
{
if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) == null)
{
return null;
}
ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
MethodInfo genericMethod =
OrderByDescendingMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) ret;
}
}
i am getting properties via reflection and i was doing like this to iterate on the list.
private string HandleListProperty(object oldObject, object newObject, string difference, PropertyInfo prop)
{
var oldList = prop.GetValue(oldObject, null) as IList;
var newList = prop.GetValue(newObject, null) as IList;
if (prop.PropertyType == typeof(List<DataModel.ScheduleDetail>))
{
List<DataModel.ScheduleDetail> ScheduleDetailsOld = oldList as List<DataModel.ScheduleDetail>;
List<DataModel.ScheduleDetail> ScheduleDetailsNew = newList as List<DataModel.ScheduleDetail>;
var groupOldSchedules = ScheduleDetailsOld
.GroupBy(x => x.HomeHelpID)
.SelectMany(s => s.DistinctBy(d => d.HomeHelpID)
.Select(h => new { h.HomeHelpID, h.HomeHelpName }));
var groupNewSchedules = ScheduleDetailsNew
.GroupBy(x => x.HomeHelpID)
.SelectMany(s => s.DistinctBy(d => d.HomeHelpID)
.Select(h => new { h.HomeHelpID, h.HomeHelpName }));
var AddedHomeHelp = string.Join(",", groupNewSchedules
.Where(x => x.HomeHelpID != null && !groupOldSchedule
.Any(y => y.HomeHelpID == x.HomeHelpID))
.Select(x => "\"<strong>" + x.HomeHelpName + "\"</strong>"));
var RemovedHomeHelp = string.Join(",", groupOldSchedules
.Where(x => x.HomeHelpID != null && groupNewSchedules
.Any(y => y.HomeHelpID != x.HomeHelpID))
.Select(x => "\"<strong>"+x.HomeHelpName+"\"</strong>"));
difference += string.IsNullOrWhiteSpace(RemovedHomeHelp) ? string.Empty : "<strong>HomeHelp</strong> " + RemovedHomeHelp + " Removed<br/>";
difference += string.IsNullOrWhiteSpace(AddedHomeHelp) ? string.Empty : "<strong>HomeHelp</strong> " + AddedHomeHelp + "Added<br/>";
}
}
Now i am making it generic because there will be coming different types of Lists and i don't want to put if conditions this way i want to write
generic code to handle any type of list.
I came up with this way:
private void HandleListProperty(object oldObject, object newObject, string difference, PropertyInfo prop)
{
var oldList = prop.GetValue(oldObject, null) as IList;
var newList = prop.GetValue(newObject, null) as IList;
var ListType = prop.PropertyType;
var MyListInstance = Activator.CreateInstance(ListType);
MyListInstance = oldList;
}
i am able to get the items in MyListInstance but as the type will come at runtime i am not getting how to write linq query to filter them, any idea how to do.
You want to compare lists. As I understand it, the 2 lists you compare will always be of the same type but one time it might be 2 lists of X and another time it might be 2 lists of Y.
The LINQ Except method is great for this. By default it compares if the items are the same exact reference but with a custom comparer, it can compare based in the ID property or anything else you want.
You need to determine how you consider 2 items to be equal and create a custom comparer that implements IEqualityComparer<T> like below.
public class CompareSchedules : IEqualityComparer<ScheduleDetail>
{
public bool Equals(ScheduleDetail x, ScheduleDetail y)
{
return x.HomeHelpID == y.HomeHelpID;
}
public int GetHashCode(ScheduleDetail obj)
{
return obj.HomeHelpID;
}
}
public static class SuperDuperListComparer
{
public class ListCompareResults<T>
{
public List<T> RemovedItems { get; set; }
public List<T> AddedItems { get; set; }
}
public static ListCompareResults<T> CompareLists<T>(IList<T> list1, IList<T> list2, IEqualityComparer<T> comparer)
{
var addedItems = list2.Except(list1, comparer).ToList();
var removedItems = list1.Except(list2, comparer).ToList();
return new ListCompareResults<T>
{
AddedItems = addedItems,
RemovedItems = removedItems
};
}
public static ListCompareResults<T> CompareLists<T>(IList<T> list1, IList<T> list2)
{
return CompareLists<T>(list1, list2, EqualityComparer<T>.Default);
}
}
and here is some example code.
[TestMethod]
public void TestListComparer()
{
var list1 = new List<ScheduleDetail> {
new ScheduleDetail { HomeHelpID = 1},
new ScheduleDetail { HomeHelpID = 3}
};
var list2 = new List<ScheduleDetail> {
new ScheduleDetail { HomeHelpID = 1},
new ScheduleDetail { HomeHelpID = 5}
};
var comparison = SuperDuperListComparer.CompareLists(list1, list2, new CompareSchedules());
}
public class ScheduleDetail
{
public int HomeHelpID { get; set; }
}
I write this extensions method for wherein;
public static IQueryable<T> WhereIn<T, TValue>(this IQueryable<T> query,
IEnumerable<TValue> values, params Expression<Func<T, TValue>>[] valueSelectors)
{
return query.Where(BuildContainsExpression(values, valueSelectors));
}
private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
IEnumerable<TValue> values, params Expression<Func<TElement, TValue>>[] valueSelector)
{
if (null == valueSelector)
{
throw new ArgumentNullException("valueSelector");
}
if (null == values)
{
throw new ArgumentNullException("values");
}
Expression<Func<TElement, bool>> predicate = null;
for (int i = 0; i < valueSelector.Count(); i++)
{
var param = valueSelector[i];
ParameterExpression genericParameter = param.Parameters[0];
TValue[] enumerable = values as TValue[] ?? values.ToArray();
if (!enumerable.Any())
{
return e => false;
}
if (!enumerable.Any())
{
return e => false;
}
IEnumerable<Expression> equals = enumerable.Select(value => (Expression)Expression.Equal(param.Body, Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate(Expression.Or);
Expression<Func<TElement, bool>> partialPredicate = Expression.Lambda<Func<TElement, bool>>(body, genericParameter);
if (predicate == null)
predicate = partialPredicate;
else
{
var orElse = Expression.OrElse(predicate.Body, partialPredicate.Body);
predicate = Expression.Lambda<Func<TElement, bool>>(orElse, genericParameter);
}
}
if (predicate != null)
return predicate;
return null;
}
predicate result is = {p => ((((Convert(p.Id) == 1) Or (Convert(p.Id) == 3)) Or (Convert(p.Id) == 5)) OrElse (((p.MemberId == 1) Or (p.MemberId == 3)) Or (p.MemberId == 5)))}
public class OrderDto { public string Id {get;set;} }
my input is:
var list = new List<int?> {1, 3, 5};
var orderList = Entity.Order.AsQueryable().WhereIn(list, p => p.Id, p => p.MemberId).Select(t => new OrderDto {Id =t.Id}); –
But I get this error;
variable 'p' of type 'OrderDto' referenced from scope '', but it is not defined
How can I fix it?
You are re-using the parameters from the given expressions.
ParameterExpression genericParameter = param.Parameters[0];
When you create new expressions, you also have to create new parameters.
ParameterExpression genericParameter = new ParameterExpression(...);
See here for more information from the master.
Is it possible to avoid duplication of this method for each string field in the model I want to check for a match? If MyModel is abstracted then obviously the MyModelField in the lambda expression is not recognized anymore, so I'm thinking maybe some kind of reflection to access the field by name?
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => s.MyModelField.ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => s.MyModelField.ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => s.MyModelField.ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => s.MyModelField.ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
EDIT
I need to call the method for dynamically build predicates to use with Entity Framework queries.
If you plan to use result of MatchMyModelFieldByStrategy with Entity Framework or LINQ2SQL, the selector must be an expression instead of delegate, because underlying LINQ providers won't recognize delegate during building entity command text.
Hence, you have to build expression yourself, something like this:
(assuming, that you have similar types:)
enum SearchStrategy
{
Contains,
StartsWith,
EndsWith,
Equals
}
class SearchItem
{
public SearchStrategy SearchStrategy { get; set; }
public string Value { get; set; }
}
Here's the code, which builds the filtering expression:
static class QueryBuilder
{
private static readonly Lazy<MethodInfo> toLowerMethodInfo;
private static readonly Dictionary<SearchStrategy, Lazy<MethodInfo>> searchStrategyToMethodInfoMap;
static QueryBuilder()
{
toLowerMethodInfo = new Lazy<MethodInfo>(() => typeof(string).GetMethod("ToLower", new Type[0]));
searchStrategyToMethodInfoMap = new Dictionary<SearchStrategy, Lazy<MethodInfo>>
{
{
SearchStrategy.Contains,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("Contains", new[] { typeof(string) }))
},
{
SearchStrategy.StartsWith,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) }))
},
{
SearchStrategy.EndsWith,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) }))
},
{
SearchStrategy.Equals,
new Lazy<MethodInfo>(() => typeof(string).GetMethod("Equals", new[] { typeof(string) }))
},
};
}
public static Expression<Func<T, bool>> MatchMyModelFieldByStrategy<T>(SearchItem searchItem, Expression<Func<T, string>> selector)
{
// "doe"
var searchItemKey = searchItem.Value.ToLower();
// _.Name.ToLower()
var toLowerCallExpr = Expression.Call(selector.Body, toLowerMethodInfo.Value);
// a method we shall use for searching
var searchMethodInfo = searchStrategyToMethodInfoMap[searchItem.SearchStrategy].Value;
// _ => _.Name.ToLower().SomeSearchMethod("doe")
return Expression.Lambda<Func<T, bool>>(
Expression.Call(toLowerCallExpr, searchMethodInfo, Expression.Constant(searchItemKey)),
selector.Parameters);
}
}
I've added a little laziness to cache reflection results, because for every MatchMyModelFieldByStrategy call they'll be the same.
Now the test entity type:
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
... and the sample code:
static void Main(string[] args)
{
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.Contains, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.StartsWith, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.EndsWith, Value = "doe" }, _ => _.Name));
Console.WriteLine(QueryBuilder.MatchMyModelFieldByStrategy<MyEntity>(
new SearchItem { SearchStrategy = SearchStrategy.Equals, Value = "doe" }, _ => _.Name));
Console.ReadLine();
}
You could provide a property selector function as a parameter. For example:
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, string> propertySelector)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => propertySelector.Invoke(s).ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => propertySelector.Invoke(s).ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => propertySelector.Invoke(s).ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => propertySelector.Invoke(s).ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
Which can be used like this:
var matchExpression = MatchMyModelFieldByStrategy(someSearchItem, model => model.MyModelField);
You can define selector for target field:
private Expression<Func<MyModel, bool>> MatchMyModelFieldByStrategy(SearchItem searchItem, Func<MyModel, String> selector)
{
var searchItemKey = searchItem.Value.ToLower();
Expression<Func<MyModel, bool>> defaultExp = s => selector(s).ToLower().Contains(searchItemKey);
switch (searchItem.SearchStrategy)
{
case StrStrategy.Contains:
return defaultExp;
case StrStrategy.StartsWith:
return s => selector(s).ToLower().StartsWith(searchItemKey);
case StrStrategy.EndsWith:
return s => selector(s).ToLower().EndsWith(searchItemKey);
case StrStrategy.Equals:
return s => selector(s).ToLower().Equals(searchItemKey);
}
return defaultStrat;
}
And use it this way:
MatchMyModelFieldByStrategy(searchItem, x=>x.MyModelField);
I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}