Pass orderBy or OrderByDescending as parameter - c#

I have method like this :
GetUsallyOpeningClosingHour(Func<OpeningDay, TimeSpan> groupByRule)
{
var openingClosingHours = listOfSpecificDayOfWeek.GroupBy(groupByRule).OrderByDescending(x => x.Key);
}
and the problem is that I can't stick all the time with OrderByDescending depends on groupByRule parameter sometimes it has to be orderByDescending or OrderBy
I don't want to depend on this parameter, so I could pass another one for that,
Right now I call my method this way:
GetUsallyOpeningClosingHour(x => x.From)
or
GetUsallyOpeningClosingHour(x => x.To)
How can I pass orderBy type as well ?

The simplest way is adding a parameter, which will specify an order in your collection.
public void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
bool orderByDesc = false)
{
var groupedDays = listOfSpecificDayOfWeek.GroupBy(groupByRule);
var openingClosingHours =
orderByDesc
? groupedDays.OrderByDescending(x => x.Key)
: groupedDays.OrderBy(x => x.Key);
}
It could be a boolean or custom enum (I prefer enum, because it actually specifies a kind of ordering operation, while boolean specifies whether collection should be ordered by desc or not).
public enum OrderingType
{
Ascending,
Descending,
None
}
Or you could provide an additional Func, which will perform an ordering operation. But its signature will be awkward.
public static void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
Func<IEnumerable<IGrouping<TimeSpan, OpeningDay>>,
IEnumerable<IGrouping<TimeSpan, OpeningDay>>> orderBy)
{
var groupedDays = listOfSpecificDayOfWeek.GroupBy(groupByRule);
var openingClosingHours = orderBy(groupedDays);
}

I guess you could create your own OrderBy extension that let you select ascending/descending based on a parameter.
Something like this:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
bool descending
)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}
You can also use an enum instead of the boolean to make things more readable when calling this method.

This is the most direct way to parameterise for OrderBy and OrderByDescending. Fortunately the type can be inferred for you by Visual Studio; unfortunately the type is long to write out. I added the static void and the initializer for listOfSpecificDayOfWeek so that this is easy to paste into a program for testing.
static void GetUsallyOpeningClosingHour(
Func<OpeningDay, TimeSpan> groupByRule,
Func<IEnumerable<IGrouping<TimeSpan, OpeningDay>>,
Func<IGrouping<TimeSpan, OpeningDay>, TimeSpan>,
IOrderedEnumerable<IGrouping<TimeSpan, OpeningDay>>> order)
{
IEnumerable<OpeningDay> listOfSpecificDayOfWeek = null;
var openingClosingHours = order(listOfSpecificDayOfWeek.GroupBy(groupByRule), x => x.Key);
}
You can call this function like this:
GetUsallyOpeningClosingHour(x => x.From, Enumerable.OrderByDescending);
GetUsallyOpeningClosingHour(x => x.From, Enumerable.OrderBy);
As other answers indicate, you can also just use a boolean flag to indicate ascending or descending order.

You would have to pass in a parameter as there's no way for the method to know which direction you want to sort in based only on the parameter (eg. From/To).
public [return-type] GetUsallyOpeningClosingHour(Func<OpeningDay, TimeSpan> groupByRule, bool isAscending)
{
var openingClosingHours = listOfSpecificDayOfWeek.GroupBy(groupByRule);
if (isAscending)
{
openingClosingHours = openingClosingHours.OrderBy(x => x.Key);
}
else
{
openingClosingHours = openingClosingHours.OrderByDescending(x => x.Key);
}
// Return openingClosingHours? It's not clear how you're using this variable.
}

public List<Book> Books(string orderField, bool desc, int skip, int take)
{
var propertyInfo = typeof(Book).GetProperty(orderField);
return _context.Books
.Where(...)
.OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
.ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
.Skip(skip)
.Take(take)
.ToList();
}

this is my code sample:
public IQueryable<T> GetAllbySearch(
int pageNumber = 1, int pageSize = 10,
Dictionary<string, dynamic> filterParams = null,
Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null,
bool allIncluded = false
, Func<IQueryable<T>, IOrderedQueryable<T>> order = null)
{
var query = _entity.AsQueryable();
if (include != null && !allIncluded)
{
query = include(query);
}
if (allIncluded && include == null)
{
foreach (var property in _context.Model.FindEntityType(typeof(T)).GetNavigations()
.Where(r => !r.IsCollection()))
query = query.Include(property.Name);
}
if (filterParams != null && filterParams.Any())
{
if (filterParams.Any(r => r.Value != null))
{
var expression = GetSearchFilter(filterParams);
if (order != null)
{
return order(query.Where(expression));
}
else
{
return query.Where(expression));
}
}
}
if (order != null)
{
return order(query);
}
else
{
return query;
}
}

Related

Cannot access variable on mapping (C#)

I have a method on the back end, that gets values related to a foreign key of the table.
Those foreign keys can be nullable, but one of those keys always will have value.
Here is method
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
if (landlordId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
At this row, I get an error Cannot resolve symbol query
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
How do I need to rewrite my method?
Declare and initialise your variable. Additionally I would re-write you method like so:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var query = _quoteRepository.GetAll();
if (landlordId.HasValue)
{
query = query.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = query.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = query .Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Also taken from this answer, you can create a WhereIf extension to clean up the if statements.
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
Making your code look like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var list = await _quoteRepository.GetAll()
.WhereIf(landlordId.HasValue, x => x.LandlordId == landlordId)
.WhereIf(agentId.HasValue, x => x.AgentId == agentId)
.WhereIf(propertyTenantId.HasValue, x => x.PropertyTenantId == propertyTenantId)
.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id)
.ToListAsync();
return new ListResultDto<QuoteListDto>(list);
}
Your problem is variable scope. When you define a variable it is only visible in the scope you define it in.
You define three different query variables in a local scope. None of them are accessible where you try to use it.
You need to define it before using it, something like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
IQueryable<Quote> query = null;
if (landlordId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Of course all of your queries should be of the same type. Otherwise you will have to define and execute them in the local scopes.
You should probably also add some error handling of the case where query is null, when you try to use it.

Check List<T> for duplications with optional words to exclude

I have a method as below. Method return either false/true either when list contains duplicates or not. I would like to extend my method to say for instance (optional) that i want to exclude specific items from check. For instance i want to check entire list as it is now or i want to say for instance exclude: string.empty items or for instance string.empty and "some word". Is it possible?
public static bool IsListContainsDuplicates<T>(List<T> list)
{
return list.GroupBy(n => n).Any(c => c.Count() > 1);
}
public static bool ContainsDuplicates<T>(this IEnumerable<T> items, IEnumerable<T> itemsToExclude = null)
{
if (itemsToExclude == null) itemsToExclude = Enumerable.Empty<T>();
return items.Except(itemsToExclude)
.GroupBy(n => n)
.Any(c => c.Count() > 1);
}
But i'd prefer this implementation because it's more performant:
public static bool ContainsDuplicates<T>(this IEnumerable<T> items, IEnumerable<T> itemsToExclude = null)
{
if (itemsToExclude == null) itemsToExclude = Enumerable.Empty<T>();
HashSet<T> set = new HashSet<T>();
return !items.Except(itemsToExclude).All(set.Add);
}
Instead of making your method more complicated, you should open it more to combine it with others:
public static class MyLinqMethods
{
public static bool HasDuplicates<T>(this IEnumerable<T> sequence)
{
return sequence.GroupBy(n => n).Any(c => c.Count() > 1);
}
}
Now you can use it with Linq:
var original = new[] { string.Empty, "Hello", "World", string.Empty };
var duplicatesInOriginal = original.HasDuplicates();
var duplicatesIfStringEmptyIsIgnored = original.Where(o => o != string.Empty).HasDuplicates();
You can use Except(). From MSDN:
Produces the set difference of two sequences by using the default
equality comparer to compare values.
return list.Except(listToExclude).GroupBy(n => n).Any(c => c.Count() > 1);
This will also help, using a 'params' in arguments and then doing Except()
public static bool IsListContainsDuplicates<T>(List<T> list, params T[] optional)
{
return list.Except(optional).GroupBy(n => n).Any(c => c.Count() > 1);
}
You can call like this if you doesn't want to exclude anything:
IsListContainsDuplicates(list)
Else, just pass the params values, for example, if the list is an integer list then,
IsListContainsDuplicates(list,5,4)

LINQ where condition with dynamic column

I have this code
// IQueryable<General> query
if (columnName == "Column1")
{
query = query.Where(x => x.Column1 == searchValue);
}
else if (columnName == "Column2")
{
query = query.Where(x => x.Column2 == searchValue);
}
else if (columnName == "Column3")
{
query = query.Where(x => x.Column3 == searchValue);
}
else if (columnName == "Column4")
{
query = query.Where(x => x.Column4 == searchValue);
}
// next zilions columns to come
// ...
and my question is. How can i past x.Column as a parameter inside ".Where" condition ?
You can create a predicate manually. Use this method:
public static Expression<Func<General, bool>> CreatePredicate(string columnName, object searchValue)
{
var xType = typeof(General);
var x = Expression.Parameter(xType, "x");
var column = xType.GetProperties().FirstOrDefault(p => p.Name == columnName);
var body = column == null
? (Expression) Expression.Constant(true)
: Expression.Equal(
Expression.PropertyOrField(x, columnName),
Expression.Constant(searchValue));
return Expression.Lambda<Func<General, bool>>(body, x);
}
Now you can apply your predicate:
IQueryable<General> query = //
var predicate = CreatePredicate(columnName , searchValue);
query = query.Where(predicate);
You could use reflection and extension methods. As a rough example:
public class Foo
{
public int Column1 { get; set; }
public int Column2 { get; set; }
...
}
public static class FooExtensions
{
// I would use the actual type here instead of object if you know the type.
public static object GetProperyValue(this Foo foo, string columnName)
{
var propertyInfo = foo.GetType().GetProperty(columnName);
var value = propertyInfo.GetValue(foo);
// as well as cast value to the type
return value;
}
}
...
query = query.Where(x => x.GetProperyValue(columnName) == searchValue);
...
As a side note, that is not a well-designed query because every time you add a column to your model, you'd need to update your if-else. It violates the O in SOLID.
You can either use Reflection to retrieve the property via name
x.GetType().GetProperty(propertyName,BindingFlags).SetValue(x,value)
// propertyName = "Column1" for example
// BindingFlags are most likely Instance, Public and Property (IIRC)
or pass in the PropertyInfo directly into the method as a parameter. Your choice depends on the abstraction level you want to expose to consumers of your method.

How to sort List<T> based on column provided as input [duplicate]

I have the name of the "sort by property" in a string. I will need to use Lambda/Linq to sort the list of objects.
Ex:
public class Employee
{
public string FirstName {set; get;}
public string LastName {set; get;}
public DateTime DOB {set; get;}
}
public void Sort(ref List<Employee> list, string sortBy, string sortDirection)
{
//Example data:
//sortBy = "FirstName"
//sortDirection = "ASC" or "DESC"
if (sortBy == "FirstName")
{
list = list.OrderBy(x => x.FirstName).toList();
}
}
Instead of using a bunch of ifs to check the fieldname (sortBy), is there a cleaner way of doing the sorting
Is sort aware of datatype?
This can be done as
list.Sort( (emp1,emp2)=>emp1.FirstName.CompareTo(emp2.FirstName) );
The .NET framework is casting the lambda (emp1,emp2)=>int as a Comparer<Employee>.
This has the advantage of being strongly typed.
If you need the descending/reverse order invert the parameters.
list.Sort( (emp1,emp2)=>emp2.FirstName.CompareTo(emp1.FirstName) );
One thing you could do is change Sort so it makes better use of lambdas.
public enum SortDirection { Ascending, Descending }
public void Sort<TKey>(ref List<Employee> list,
Func<Employee, TKey> sorter, SortDirection direction)
{
if (direction == SortDirection.Ascending)
list = list.OrderBy(sorter);
else
list = list.OrderByDescending(sorter);
}
Now you can specify the field to sort when calling the Sort method.
Sort(ref employees, e => e.DOB, SortDirection.Descending);
You could use Reflection to get the value of the property.
list = list.OrderBy( x => TypeHelper.GetPropertyValue( x, sortBy ) )
.ToList();
Where TypeHelper has a static method like:
public static class TypeHelper
{
public static object GetPropertyValue( object obj, string name )
{
return obj == null ? null : obj.GetType()
.GetProperty( name )
.GetValue( obj, null );
}
}
You might also want to look at Dynamic LINQ from the VS2008 Samples library. You could use the IEnumerable extension to cast the List as an IQueryable and then use the Dynamic link OrderBy extension.
list = list.AsQueryable().OrderBy( sortBy + " " + sortDirection );
This is how I solved my problem:
List<User> list = GetAllUsers(); //Private Method
if (!sortAscending)
{
list = list
.OrderBy(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
else
{
list = list
.OrderByDescending(r => r.GetType().GetProperty(sortBy).GetValue(r,null))
.ToList();
}
Building the order by expression can be read here
Shamelessly stolen from the page in link:
// First we define the parameter that we are going to use
// in our OrderBy clause. This is the same as "(person =>"
// in the example above.
var param = Expression.Parameter(typeof(Person), "person");
// Now we'll make our lambda function that returns the
// "DateOfBirth" property by it's name.
var mySortExpression = Expression.Lambda<Func<Person, object>>(Expression.Property(param, "DateOfBirth"), param);
// Now I can sort my people list.
Person[] sortedPeople = people.OrderBy(mySortExpression).ToArray();
You could use reflection to access the property.
public List<Employee> Sort(List<Employee> list, String sortBy, String sortDirection)
{
PropertyInfo property = list.GetType().GetGenericArguments()[0].
GetType().GetProperty(sortBy);
if (sortDirection == "ASC")
{
return list.OrderBy(e => property.GetValue(e, null));
}
if (sortDirection == "DESC")
{
return list.OrderByDescending(e => property.GetValue(e, null));
}
else
{
throw new ArgumentOutOfRangeException();
}
}
Notes
Why do you pass the list by reference?
You should use a enum for the sort direction.
You could get a much cleaner solution if you would pass a lambda expression
specifying the property to sort by instead of the property name as a string.
In my example list == null will cause a NullReferenceException, you should catch this case.
Sort uses the IComparable interface, if the type implements it.
And you can avoid the ifs by implementing a custom IComparer:
class EmpComp : IComparer<Employee>
{
string fieldName;
public EmpComp(string fieldName)
{
this.fieldName = fieldName;
}
public int Compare(Employee x, Employee y)
{
// compare x.fieldName and y.fieldName
}
}
and then
list.Sort(new EmpComp(sortBy));
Answer for 1.:
You should be able to manually build an expression tree that can be passed into OrderBy using the name as a string. Or you could use reflection as suggested in another answer, which might be less work.
Edit: Here is a working example of building an expression tree manually. (Sorting on X.Value, when only knowing the name "Value" of the property). You could (should) build a generic method for doing it.
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
private static readonly Random rand = new Random();
static void Main(string[] args)
{
var randX = from n in Enumerable.Range(0, 100)
select new X { Value = rand.Next(1000) };
ParameterExpression pe = Expression.Parameter(typeof(X), "value");
var expression = Expression.Property(pe, "Value");
var exp = Expression.Lambda<Func<X, int>>(expression, pe).Compile();
foreach (var n in randX.OrderBy(exp))
Console.WriteLine(n.Value);
}
public class X
{
public int Value { get; set; }
}
}
Building an expression tree requires you to know the particpating types, however. That might or might not be a problem in your usage scenario. If you don't know what type you should be sorting on, it will propably be easier using reflection.
Answer for 2.:
Yes, since Comparer<T>.Default will be used for the comparison, if you do not explicitly define the comparer.
The solution provided by Rashack does not work for value types (int, enums, etc.) unfortunately.
For it to work with any type of property, this is the solution I found:
public static Expression<Func<T, object>> GetLambdaExpressionFor<T>(this string sortColumn)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "x");
var body = Expression.PropertyOrField(parameterExpression, sortColumn);
var convertedBody = Expression.MakeUnary(ExpressionType.Convert, body, typeof(object));
var expression = Expression.Lambda<Func<T, object>>(convertedBody, new[] { parameterExpression });
return expression;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
public static class EnumerableHelper
{
static MethodInfo orderBy = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName)
{
var pi = typeof(TSource).GetProperty(propertyName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
var sourceParam = Expression.Parameter(typeof(IEnumerable<TSource>), "source");
return
Expression.Lambda<Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>>>
(
Expression.Call
(
orderBy.MakeGenericMethod(typeof(TSource), pi.PropertyType),
sourceParam,
Expression.Lambda
(
typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType),
Expression.Property(selectorParam, pi),
selectorParam
)
),
sourceParam
)
.Compile()(source);
}
public static IEnumerable<TSource> OrderBy<TSource>(this IEnumerable<TSource> source, string propertyName, bool ascending)
{
return ascending ? source.OrderBy(propertyName) : source.OrderBy(propertyName).Reverse();
}
}
Another one, this time for any IQueryable:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class IQueryableHelper
{
static MethodInfo orderBy = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderBy" && x.GetParameters().Length == 2).First();
static MethodInfo orderByDescending = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(x => x.Name == "OrderByDescending" && x.GetParameters().Length == 2).First();
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, params string[] sortDescriptors)
{
return sortDescriptors.Length > 0 ? source.OrderBy(sortDescriptors, 0) : source;
}
static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string[] sortDescriptors, int index)
{
if (index < sortDescriptors.Length - 1) source = source.OrderBy(sortDescriptors, index + 1);
string[] splitted = sortDescriptors[index].Split(' ');
var pi = typeof(TSource).GetProperty(splitted[0], BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.IgnoreCase);
var selectorParam = Expression.Parameter(typeof(TSource), "keySelector");
return source.Provider.CreateQuery<TSource>(Expression.Call((splitted.Length > 1 && string.Compare(splitted[1], "desc", StringComparison.Ordinal) == 0 ? orderByDescending : orderBy).MakeGenericMethod(typeof(TSource), pi.PropertyType), source.Expression, Expression.Lambda(typeof(Func<,>).MakeGenericType(typeof(TSource), pi.PropertyType), Expression.Property(selectorParam, pi), selectorParam)));
}
}
You can pass multiple sort criteria, like this:
var q = dc.Felhasznalos.OrderBy(new string[] { "Email", "FelhasznaloID desc" });
Adding to what #Samuel and #bluish did. This is much shorter as the Enum was unnecessary in this case. Plus as an added bonus when the Ascending is the desired result, you can pass only 2 parameters instead of 3 since true is the default answer to the third parameter.
public void Sort<TKey>(ref List<Person> list, Func<Person, TKey> sorter, bool isAscending = true)
{
list = isAscending ? list.OrderBy(sorter) : list.OrderByDescending(sorter);
}
If you get sort column name and sort direction as string and don't want to use switch or if\else syntax to determine column, then this example may be interesting for you:
private readonly Dictionary<string, Expression<Func<IuInternetUsers, object>>> _sortColumns =
new Dictionary<string, Expression<Func<IuInternetUsers, object>>>()
{
{ nameof(ContactSearchItem.Id), c => c.Id },
{ nameof(ContactSearchItem.FirstName), c => c.FirstName },
{ nameof(ContactSearchItem.LastName), c => c.LastName },
{ nameof(ContactSearchItem.Organization), c => c.Company.Company },
{ nameof(ContactSearchItem.CustomerCode), c => c.Company.Code },
{ nameof(ContactSearchItem.Country), c => c.CountryNavigation.Code },
{ nameof(ContactSearchItem.City), c => c.City },
{ nameof(ContactSearchItem.ModifiedDate), c => c.ModifiedDate },
};
private IQueryable<IuInternetUsers> SetUpSort(IQueryable<IuInternetUsers> contacts, string sort, string sortDir)
{
if (string.IsNullOrEmpty(sort))
{
sort = nameof(ContactSearchItem.Id);
}
_sortColumns.TryGetValue(sort, out var sortColumn);
if (sortColumn == null)
{
sortColumn = c => c.Id;
}
if (string.IsNullOrEmpty(sortDir) || sortDir == SortDirections.AscendingSort)
{
contacts = contacts.OrderBy(sortColumn);
}
else
{
contacts = contacts.OrderByDescending(sortColumn);
}
return contacts;
}
Solution based on using Dictionary that connects needed for sort column via Expression> and its key string.

Creating a dynamic sort method for linq to entities [duplicate]

I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a SQL-like string (e.g. OrderBy("Name, Age DESC")) for ordering. Unfortunately, the method included only works on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?
Just stumbled into this oldie...
To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.
To get it working with IEnumerable<T> you could add some wrapper methods that go via AsQueryable - but the code below is the core Expression logic needed.
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(
this IQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(
this IOrderedQueryable<T> source,
string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(
IQueryable<T> source,
string property,
string methodName)
{
string[] props = property.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);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = 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[] {source, lambda});
return (IOrderedQueryable<T>)result;
}
Edit: it gets more fun if you want to mix that with dynamic - although note that dynamic only applies to LINQ-to-Objects (expression-trees for ORMs etc can't really represent dynamic queries - MemberExpression doesn't support it). But here's a way to do it with LINQ-to-Objects. Note that the choice of Hashtable is due to favorable locking semantics:
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Runtime.CompilerServices;
static class Program
{
private static class AccessorCache
{
private static readonly Hashtable accessors = new Hashtable();
private static readonly Hashtable callSites = new Hashtable();
private static CallSite<Func<CallSite, object, object>> GetCallSiteLocked(
string name)
{
var callSite = (CallSite<Func<CallSite, object, object>>)callSites[name];
if(callSite == null)
{
callSites[name] = callSite = CallSite<Func<CallSite, object, object>>
.Create(Binder.GetMember(
CSharpBinderFlags.None,
name,
typeof(AccessorCache),
new CSharpArgumentInfo[] {
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None,
null)
}));
}
return callSite;
}
internal static Func<dynamic,object> GetAccessor(string name)
{
Func<dynamic, object> accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
lock (accessors )
{
accessor = (Func<dynamic, object>)accessors[name];
if (accessor == null)
{
if(name.IndexOf('.') >= 0) {
string[] props = name.Split('.');
CallSite<Func<CallSite, object, object>>[] arr
= Array.ConvertAll(props, GetCallSiteLocked);
accessor = target =>
{
object val = (object)target;
for (int i = 0; i < arr.Length; i++)
{
var cs = arr[i];
val = cs.Target(cs, val);
}
return val;
};
} else {
var callSite = GetCallSiteLocked(name);
accessor = target =>
{
return callSite.Target(callSite, (object)target);
};
}
accessors[name] = accessor;
}
}
}
return accessor;
}
}
public static IOrderedEnumerable<dynamic> OrderBy(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> OrderByDescending(
this IEnumerable<dynamic> source,
string property)
{
return Enumerable.OrderByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenBy(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenBy<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
public static IOrderedEnumerable<dynamic> ThenByDescending(
this IOrderedEnumerable<dynamic> source,
string property)
{
return Enumerable.ThenByDescending<dynamic, object>(
source,
AccessorCache.GetAccessor(property),
Comparer<object>.Default);
}
static void Main()
{
dynamic a = new ExpandoObject(),
b = new ExpandoObject(),
c = new ExpandoObject();
a.X = "abc";
b.X = "ghi";
c.X = "def";
dynamic[] data = new[] {
new { Y = a },
new { Y = b },
new { Y = c }
};
var ordered = data.OrderByDescending("Y.X").ToArray();
foreach (var obj in ordered)
{
Console.WriteLine(obj.Y.X);
}
}
}
Too easy without any complication:
Add using System.Linq.Dynamic; at the top.
Use vehicles = vehicles.AsQueryable().OrderBy("Make ASC, Year DESC").ToList();
Edit: to save some time, the System.Linq.Dynamic.Core (System.Linq.Dynamic is deprecated) assembly is not part of the framework, but can be installed from nuget: System.Linq.Dynamic.Core
Just stumbled across this question.
Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:
list.OrderBy("MyProperty DESC, MyOtherProperty ASC");
Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html
I guess it would work to use reflection to get whatever property you want to sort on:
IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
where some criteria
orderby GetPropertyValue(enumerable,"SomeProperty")
select enumerable
private static object GetPropertyValue(object obj, string property)
{
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}
Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.
Just building on what others have said. I found that the following works quite well.
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
{
if (string.IsNullOrEmpty(queryString))
return input;
int i = 0;
foreach (string propname in queryString.Split(','))
{
var subContent = propname.Split('|');
if (Convert.ToInt32(subContent[1].Trim()) == 0)
{
if (i == 0)
input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
}
else
{
if (i == 0)
input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
else
input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
}
i++;
}
return input;
}
I was trying to do this but having problems with Kjetil Watnedal's solution because I don't use the inline linq syntax - I prefer method-style syntax. My specific problem was in trying to do dynamic sorting using a custom IComparer.
My solution ended up like this:
Given an IQueryable query like so:
List<DATA__Security__Team> teams = TeamManager.GetTeams();
var query = teams.Where(team => team.ID < 10).AsQueryable();
And given a run-time sort field argument:
string SortField; // Set at run-time to "Name"
The dynamic OrderBy looks like so:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));
And that's using a little helper method called GetReflectedPropertyValue():
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
One last thing - I mentioned that I wanted the OrderBy to use custom IComparer - because I wanted to do Natural sorting.
To do that, I just alter the OrderBy to:
query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());
See this post for the code for NaturalSortComparer().
I've stumble this question looking for Linq multiple orderby clauses
and maybe this was what the author was looking for
Here's how to do that:
var query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
Use dynamic linq
just add using System.Linq.Dynamic;
And use it like this to order all your columns:
string sortTypeStr = "ASC"; // or DESC
string SortColumnName = "Age"; // Your column name
query = query.OrderBy($"{SortColumnName} {sortTypeStr}");
After a lot of searching this worked for me:
public static IEnumerable<TEntity> OrderBy<TEntity>(this IEnumerable<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, property.PropertyType },
source.AsQueryable().Expression,
Expression.Quote(orderByExpression));
return source.AsQueryable().Provider.CreateQuery<TEntity>(resultExpression);
}
First Install Dynamic
Tools --> NuGet Package Manager --> Package Manager Console
install-package System.Linq.Dynamic
Add Namespace using System.Linq.Dynamic;
Now you can use OrderBy("Name, Age DESC")
You could add it:
public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
//parse the string into property names
//Use reflection to get and sort by properties
//something like
foreach( string propname in queryString.Split(','))
input.OrderBy( x => GetPropertyValue( x, propname ) );
// I used Kjetil Watnedal's reflection example
}
The GetPropertyValue function is from Kjetil Watnedal's answer
The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).
If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.
Here's something else I found interesting.
If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq
DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
orderby order.Field<DateTime>("OrderDate")
select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;
reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)
Here is one more way to do it by converting it to a DataView:
DataTable contacts = dataSet.Tables["Contact"];
DataView view = contacts.AsDataView();
view.Sort = "LastName desc, FirstName asc";
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
You can convert the IEnumerable to IQueryable.
items = items.AsQueryable().OrderBy("Name ASC");
An alternate solution uses the following class/interface. It's not truly dynamic, but it works.
public interface IID
{
int ID
{
get; set;
}
}
public static class Utils
{
public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
{
if (items.Count() == 0) return 1;
return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
}
}
Thanks to Maarten (Query a collection using PropertyInfo object in LINQ) I got this solution:
myList.OrderByDescending(x => myPropertyInfo.GetValue(x, null)).ToList();
In my case I was working on a "ColumnHeaderMouseClick" (WindowsForm) so just found the specific Column pressed and its correspondent PropertyInfo:
foreach (PropertyInfo column in (new Process()).GetType().GetProperties())
{
if (column.Name == dgvProcessList.Columns[e.ColumnIndex].Name)
{}
}
OR
PropertyInfo column = (new Process()).GetType().GetProperties().Where(x => x.Name == dgvProcessList.Columns[e.ColumnIndex].Name).First();
(be sure to have your column Names matching the object Properties)
Cheers
You can use this:
public List<Book> Books(string orderField, bool desc, int skip, int take)
{
var propertyInfo = typeof(Book).GetProperty(orderField);
return _context.Books
.Where(...)
.OrderBy(p => !desc ? propertyInfo.GetValue(p, null) : 0)
.ThenByDescending(p => desc ? propertyInfo.GetValue(p, null) : 0)
.Skip(skip)
.Take(take)
.ToList();
}
This answer is a response to the comments that need an example for the solution provided by #John Sheehan - Runscope
Please provide an example for the rest of us.
in DAL (Data Access Layer),
The IEnumerable version:
public IEnumerable<Order> GetOrders()
{
// i use Dapper to return IEnumerable<T> using Query<T>
//.. do stuff
return orders // IEnumerable<Order>
}
The IQueryable version
public IQueryable<Order> GetOrdersAsQuerable()
{
IEnumerable<Order> qry= GetOrders();
// use the built-in extension method AsQueryable in System.Linq namespace
return qry.AsQueryable();
}
Now you can use the IQueryable version to bind, for example GridView in Asp.net and benefit for sorting (you can't sort using IEnumerable version)
I used Dapper as ORM and build IQueryable version and utilized sorting in GridView in asp.net so easy.
You can define a dictionary from string to Func<> like this :
Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
{"Rank", x => x.Rank}
};
And use it like this :
yourList.OrderBy(SortParameters["Rank"]);
In this case you can dynamically sort by string.
you can do it like this for multiple order by
IOrderedEnumerable<JToken> sort;
if (query.OrderBys[0].IsDESC)
{
sort = jarry.OrderByDescending(r => (string)r[query.OrderBys[0].Key]);
}
else
{
sort = jarry.OrderBy(r =>
(string) r[query.OrderBys[0].Key]);
}
foreach (var item in query.OrderBys.Skip(1))
{
if (item.IsDESC)
{
sort = sort.ThenByDescending(r => (string)r[item.Key]);
}
else
{
sort = sort.ThenBy(r => (string)r[item.Key]);
}
}
Convert List to IEnumerable or Iquerable, add using System.LINQ.Dynamic namespace, then u can mention the property names in comma seperated string to OrderBy Method which comes by default from System.LINQ.Dynamic.
I am able to do this with the code below. No need write long and complex code.
protected void sort_array(string field_name, string asc_desc)
{
objArrayList= Sort(objArrayList, field_name, asc_desc);
}
protected List<ArrayType> Sort(List<ArrayType> input, string property, string asc_desc)
{
if (asc_desc == "ASC")
{
return input.OrderBy(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
else
{
return input.OrderByDescending(p => p.GetType()
.GetProperty(property)
.GetValue(p, null)).ToList();
}
}
If you are using Specification (such as Ardalis Specification)
using Microsoft.EntityFrameworkCore;
namespace TestExtensions;
public static class IQueryableExtensions
{
public static void ApplyOrder<T>(ISpecificationBuilder<T> query, string propertyName, bool ascendingOrder)
{
if (ascendingOrder)
query.OrderBy(T => EF.Property<object>(T!, propertyName));
else
query.OrderByDescending(T => EF.Property<object>(T!, propertyName));
}
}
With Net6 and EF
.AsQueryable().OrderBy((ColumnOrder.Column, ColumnOrder.Dir));
var result1 = lst.OrderBy(a=>a.Name);// for ascending order.
var result1 = lst.OrderByDescending(a=>a.Name);// for desc order.

Categories

Resources