LINQ select property by name [duplicate] - c#

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm attempting to use a variable inside of a LINQ select statement.
Here is an example of what I'm doing now.
using System;
using System.Collections.Generic;
using System.Linq;
using Faker;
namespace ConsoleTesting
{
internal class Program
{
private static void Main(string[] args)
{
List<Person> listOfPersons = new List<Person>
{
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person()
};
var firstNames = Person.GetListOfAFirstNames(listOfPersons);
foreach (var item in listOfPersons)
{
Console.WriteLine(item);
}
Console.WriteLine();
Console.ReadKey();
}
public class Person
{
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person()
{
FirstName = NameFaker.Name();
LastName = NameFaker.LastName();
City = LocationFaker.City();
CountryName = LocationFaker.Country();
}
public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
}
}
}
I have a Some very not DRY code with the GetListOf... Methods
i feel like i should be able to do something like this
public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
{
return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
}
but that is not vaild code. I think the key Might Relate to Creating a Func
if That is the answer how do I do that?
Here is a second attempt using refelection But this is also a no go.
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Person person = new Person();
Type t = person.GetType();
PropertyInfo prop = t.GetProperty(property);
return listOfPersons.Select(prop).Distinct().OrderBy(x =>
x).ToList();
}
I think the refection might be a DeadEnd/red herring but i thought i would show my work anyway.
Note Sample Code is simplified in reality this is used to populate a datalist via AJAX to Create an autocomplete experience. That object has 20+ properties and I can complete by writing 20+ methods but I feel there should be a DRY way to complete this. Also making this one method also would clean up my controller action a bunch also.
Question:
Given the first section of code is there a way to abstract those similar methods into a single method buy passing some object into the select Statement???
Thank you for your time.

You would have to build the select
.Select(x =>x.property).
by hand. Fortunately, it isn't a tricky one since you expect it to always be the same type (string), so:
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);
Then the Select above becomes:
.Select(lambda).
(for LINQ based on IQueryable<T>) or
.Select(lambda.Compile()).
(for LINQ based on IEnumerable<T>).
Note that anything you can do to cache the final form by property would be good.

From your examples, I think what you want is this:
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (string)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
typeof is a built-in operator in C# that you can "pass" the name of a type to and it will return the corresponding instance of Type. It works at compile-time, not runtime, so it doesn't work like normal functions.
PropertyInfo has a GetValue method that takes an object parameter. The object is which instance of the type to get the property value from. If you are trying to target a static property, use null for that parameter.
GetValue returns an object, which you must cast to the actual type.
person => (string)prop.GetValue(person) is a lamba expression that has a signature like this:
string Foo(Person person) { ... }
If you want this to work with any type of property, make it generic instead of hardcoding string.
public static List<T> GetListOfProperty<T>(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (T)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}

I would stay away from reflection and hard coded strings where possible...
How about defining an extension method that accepts a function selector of T, so that you can handle other types beside string properties
public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)
{
return instance
.Select(selector)
.Distinct()
.OrderBy(x => x)
.ToList();
}
and imagine that you have a person class that has an id property of type int besides those you already expose
public class Person
{
public int Id { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
all you need to do is fetch the results with type safe lambda selectors
var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);
Edit
As it seems you really need hardcoded strings as the property inputs, how about leaving out some dynamism and use a bit of determinism
public static List<string> Query(this IEnumerable<Person> instance, string property)
{
switch (property)
{
case "ids": return instance.Query(p => p.Id.ToString());
case "firstName": return instance.Query(p => p.FirstName);
case "lastName": return instance.Query(p => p.LastName);
case "countryName": return instance.Query(p => p.CountryName);
case "cityName": return instance.Query(p => p.City);
default: throw new Exception($"{property} is not supported");
}
}
and access the desired results as such
var cityNames = listOfPersons.Query("cityName");

You should be able to do it with Reflection. I use it something similar.
Just change your reflection try to this:
public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)
{
var ret = new List<string>();
PropertyInfo prop = typeof(Person).GetProperty(propertyName);
if (prop != null)
ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();
return ret;
}
I hope it helps.
It's based on C# 6

You can also use this. works for me.
public static class ObjectReflectionExtensions
{
public static object GetValueByName<T>(this T thisObject, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
return prop.GetValue(thisObject);
}
}
And call like this.
public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
{
return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
}

If you want to select all the values:
object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();

Related

recursive function call in foreach throwing system.stackoverflowexception

I am getting a System.StackOverflowException: 'Exception of type 'System.StackOverflowException' was thrown.' message.
My code as follows, Here I want to assign value to a variable recursively based on the condition and return the list.
public class FancyTree
{
public string title { get; set; }
public string key { get; set; }
public List<FancyTree> children { get; set; }
}
For example the FancyTree Class produces the output like parent->child or parent->parent->child or parent->parent->parent->child just like the Treeview structure.
public JsonResult EmployeesTree()
{
var output = converttoFancyTree(db.Database.GetEmployees(true));
return Json(output, JsonRequestBehavior.AllowGet);
}
public List<FancyTree> converttoFancyTree(List<EmpTable> emps)
{
var output = new List<FancyTree>();
foreach (var emp in emps)
{
var fancyTreeItem = new FancyTree();
fancyTreeItem.key = emp.EMP_ID.ToString();
fancyTreeItem.title = emp.EMP_NAME;
if (!string.IsNullOrEmpty(emp.TEAM))
{
//var empIDs = emp.TEAM?.Split(',')?.Select(Int32.Parse)?.ToList();
var tms = emp.TEAM.Split(',');
if (tms.Length > 0) {
var empIDs = new List<int>();
foreach (var t in tms)
{
empIDs.Add(int.Parse(t));
}
var TeamMembers = emps.Where(x => empIDs.Contains(x.EMP_ID)).ToList();
if (TeamMembers.Count > 0)
{
var childrens = converttoFancyTree(TeamMembers);
fancyTreeItem.children = childrens;
}
}
}
output.Add(fancyTreeItem);
}
return output;
}
I would assume your input is in the form of a plain list of objects, where each object contains the IDs of all the children, and you want to convert this to an object representation, i.e. something like:
public class Employee{
public int Id {get;}
public List<int> SubordinateIds {get;}
}
public class EmployeeTreeNode{
public IReadOnlyList<EmployeeTreeNode> Subordinates {get;} ;
public int Id {get;}
public EmployeeTreeNode(int id, IEnumerable<EmployeeTreeNode> subordinates){
Id = id;
Subordinates = subordinates;
}
To convert this to a tree representation we can start by finding the roots of the tree, i.e. employees that are not subordinate to anyone.
var allSubordinates = allEmployees.SelectMany(e => e.SubordinateIds).ToList();
var allRoots = allEmployees.Select(e => e.Id).Except(allSubordinates);
We then need an efficient way to find a specific employee by the Id, i.e. a dictionary:
var employeeById = allEmployees.ToDictionary(e => e.Id, e => e.SubordinateIds);
We can then finally do the actual recursion, and we can create a generic helper method to assist:
public static TResult MapChildren<T, TResult>(
T root,
Func<T, IEnumerable<T>> getChildren,
Func<T, IEnumerable<TResult>, TResult> map)
{
return RecurseBody(root);
TResult RecurseBody(T item) => map(item, getChildren(item).Select(RecurseBody));
}
...
var tree = allRoots.Select(r => MapChildren(
r,
id => employeeById[id],
(id, subordinates) => new EmployeeTreeNode(id, subordinates)));
This will recurse down to any employee without any subordinates, create EmployeeTreeNode for these, and then eventually traverse up the tree, creating node objects as it goes.
This assumes that there are no loops/cycles. If that is the case you do not have a tree, since trees are by definition acyclic, and the code will crash. You will instead need to handle the more general case of a graph, and this is a harder problem, and you will need to decide how the cycles should be handled.

Select only specific fields with Linq (EF core)

I have a DbContext where I would like to run a query to return only specific columns, to avoid fetching all the data.
The problem is that I would like to specify the column names with a set of strings, and I would like to obtain an IQueryable of the original type, i.e. without constructing an anonymous type.
Here is an example:
// Install-Package Microsoft.AspNetCore.All
// Install-Package Microsoft.EntityFrameworkCore
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
public class Person {
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestContext : DbContext {
public virtual DbSet<Person> Persons { get; set; }
public TestContext(DbContextOptions<TestContext> options) : base(options) {
}
}
class Program {
static void Main(string[] args) {
var builder = new DbContextOptionsBuilder<TestContext>();
builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
var context = new TestContext(builder.Options);
context.Persons.Add(new Person { FirstName = "John", LastName = "Doe" });
context.SaveChanges();
// How can I express this selecting columns with a set of strings?
IQueryable<Person> query = from p in context.Persons select new Person { FirstName = p.FirstName };
}
}
I would like to have something like this method:
static IQueryable<Person> GetPersons(TestContext context, params string[] fieldsToSelect) {
// ...
}
Is there a way I can do this?
Since you are projecting (selecting) the members of the type T to the same type T, the required Expression<Func<T, T>> can relatively easy be created with Expression class methods like this:
public static partial class QueryableExtensions
{
public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, params string[] memberNames)
{
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = memberNames
.Select(name => Expression.PropertyOrField(parameter, name))
.Select(member => Expression.Bind(member.Member, member));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
}
Expression.MemberInit is the expression equivalent of the new T { Member1 = x.Member1, Member2 = x.Member2, ... } C# construct.
The sample usage would be:
return context.Set<Person>().SelectMembers(fieldsToSelect);
This can be achieved by using Dynamic Linq.
and for .Net Core - System.Linq.Dynamic.Core
With Dynamic Linq you can pass in your SELECT and WHERE as a string.
Using your example, you could then do something like:
IQueryable<Person> query = context.Persons
.Select("new Person { FirstName = p.FirstName }");
Based on answer of Ivan I made crude version of caching function to eliminate the toll layed on us by using of reflexion. It allow as to lower this toll from milliseconds to microseconds on repeated requests (typical for DbAccess API, for example).
public static class QueryableExtensions
{
public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, IEnumerable<string> memberNames)
{
var result = QueryableGenericExtensions<T>.SelectMembers(source, memberNames);
return result;
}
}
public static class QueryableGenericExtensions<T>
{
private static readonly ConcurrentDictionary<string, ParameterExpression> _parameters = new();
private static readonly ConcurrentDictionary<string, MemberAssignment> _bindings = new();
private static readonly ConcurrentDictionary<string, Expression<Func<T, T>>> _selectors = new();
public static IQueryable<T> SelectMembers(IQueryable<T> source, IEnumerable<string> memberNames)
{
var parameterName = typeof(T).FullName;
var requestName = $"{parameterName}:{string.Join(",", memberNames.OrderBy(x => x))}";
if (!_selectors.TryGetValue(requestName, out var selector))
{
if (!_parameters.TryGetValue(parameterName, out var parameter))
{
parameter = Expression.Parameter(typeof(T), typeof(T).Name.ToLowerInvariant());
_ = _parameters.TryAdd(parameterName, parameter);
}
var bindings = memberNames
.Select(name =>
{
var memberName = $"{parameterName}:{name}";
if (!_bindings.TryGetValue(memberName, out var binding))
{
var member = Expression.PropertyOrField(parameter, name);
binding = Expression.Bind(member.Member, member);
_ = _bindings.TryAdd(memberName, binding);
}
return binding;
});
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
selector = Expression.Lambda<Func<T, T>>(body, parameter);
_selectors.TryAdd(requestName, selector);
}
return source.Select(selector);
}
}
Example of results after sequential run with same params (please note that this is NANOseconds):
SelectMembers time ... 3092214 ns
SelectMembers time ... 145724 ns
SelectMembers time ... 38613 ns
SelectMembers time ... 1969 ns
I have no idea why the time decreases gradually, not from "without cache" to "with cache", may be it is because of my environment with loop of questioning 4 servers with same request and some deep-level magic with asyncs. Repeating request produces consistent results similar to the last one +/- 1-2 microseconds.
Try this code:
string fieldsToSelect = "new Person { FirstName = p.FirstName }"; //Pass this as parameter.
public static IQueryable<Person> GetPersons(TestContext context, string fieldsToSelect)
{
IQueryable<Person> query = context.Persons.Select(fieldsToSelect);
}
I was able to do this with the package https://github.com/StefH/System.Linq.Dynamic.Core so easily.
Here is an example code.
use namespacing, using System.Linq.Dynamic.Core;
//var selectQuery = "new(Name, Id, PresentDetails.RollNo)";
var selectQuery = "new(Name, Id, PresentDetails.GuardianDetails.Name as GuardianName)";
var students = dbContext.Students
.Include(s => s.PresentDetails)
.Include(s => s.PresentDetails.GuardianDetails)
.Where(s => s.StudentStatus == "Admitted")
.Select(selectQuery);
var students = dbContext.Students
.Include(s => s.PresentDetails)
.Where(s => s.StudentStatus == "Admitted")
.Select(p => new Person()
{
Id = p.Id,
Name = p.Name
});
Why not minimize the selected columns in the regular way? this is way cleaner.

Multiple expression in LINQ select

I want combine my expression built in runtime (CustomExpression) with ordinary select clausule. Is there any way in C# to do this without manually building whole expression?
var dto = iqueryable.Select(d => new DTO()
{
X = d.X,
Y = d.Y,
Z = CustomExpression
}
Where CustomExpression is something like this:
private Expression<Func<EntityTypeFromIQueryable, string>> CustomExpression() {
get {
// there is manually built expression like this:
return x => x.Blah
}
}
You have to insert some kind of compilable placeholder (like an Extension Method) into your expression first. Then, at runtime, you can modify the expression using an Expression Visitor to replace your "placeholder" with the actual lambda expression. Since your actual expression uses different Parameters (d vs. x) you have to replace them with those of the "original" expression.
In fact, i'm playing around with such scenarios within this project, where i've tried to abstract this kind of expression plumbing. Your "combine" would then look like that:
var dto = iqueryable.ToInjectable().Select(d => new DTO()
{
X = d.X,
Y = d.Y,
Z = d.CustomExpression()
}
public static class CustomExpressions
{
[InjectLambda]
public static string CustomExpression(this EntityTypeFromIQueryable value)
{
// this function is just a placeholder
// you can implement it for non LINQ use too...
throw new NotImplementedException();
}
public static Expression<Func<EntityTypeFromIQueryable, string>> CustomExpression()
{
return x => x.Blah
}
}
The call ToInjectable() creates a lightweight proxy around the original Queryable to modify the expression before execution as described. The attribute InjectLambda marks the "placeholder" as "inject lambda here". By convention the actual expression returned by ToInjectable() gets inserted at the desired position.
You can do it in following way:
static void MultipleExpressionInSelectStatement()
{
List<person> p = new List<person>();
p.Add(new person() { name = "AB", age = 18 });
p.Add(new person() { name = "CD", age = 45 });
var dto = p.Select(d => new person()
{
name=d.name,
age=p.Select(ListExtensions.CustomExpression()).ElementAt(0)
});
}
//customExpression
public static class ListExtensions
{
public static Func<person, int> CustomExpression()
{
return x => x.age;
}
}
//Person Object
public class person
{
public string name { get; set; }
public int age { get; set; }
}

How to Convert Lambda Expression To Sql?

I am developing a small framework to access the database. I want to add a feature that makes a query using a lambda expression. How do I do this?
public class TestModel
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Repository<T>
{
// do something.
}
For example:
var repo = new Repository<TestModel>();
var query = repo.AsQueryable().Where(x => x.Name == "test");
// This query must be like this:
// SELECT * FROM testmodel WHERE name = 'test'
var list = query.ToDataSet();
// When I call ToDataSet(), it will get the dataset after running the made query.
Go on and create a LINQ Provider (I am sure you don't want to do this, anyway).
It's a lot of work, so maybe you just want to use NHibernate or Entity Framework or something like that.
If your queries are rather simple, maybe you don't need a full blown LINQ Provider. Have a look at Expression Trees (which are used by LINQ Providers).
You can hack something like this:
public static class QueryExtensions
{
public static IEnumerable<TSource> Where<TSource>(this Repo<TSource> source, Expression<Func<TSource, bool>> predicate)
{
// hacks all the way
dynamic operation = predicate.Body;
dynamic left = operation.Left;
dynamic right = operation.Right;
var ops = new Dictionary<ExpressionType, String>();
ops.Add(ExpressionType.Equal, "=");
ops.Add(ExpressionType.GreaterThan, ">");
// add all required operations here
// Instead of SELECT *, select all required fields, since you know the type
var q = String.Format("SELECT * FROM {0} WHERE {1} {2} {3}", typeof(TSource), left.Member.Name, ops[operation.NodeType], right.Value);
return source.RunQuery(q);
}
}
public class Repo<T>
{
internal IEnumerable<T> RunQuery(string query)
{
return new List<T>(); // run query here...
}
}
public class TestModel
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var repo = new Repo<TestModel>();
var result = repo.Where(e => e.Name == "test");
var result2 = repo.Where(e => e.Id > 200);
}
}
Please, don't use this as it is. This is just a quick and dirty example how expression trees can be analyzed to create SQL statements.
Why not just use Linq2Sql, NHibernate or EntityFramework...
if you want to do things like
db.Employee
.Where(e => e.Title == "Spectre")
.Set(e => e.Title, "Commander")
.Update();
or
db
.Into(db.Employee)
.Value(e => e.FirstName, "John")
.Value(e => e.LastName, "Shepard")
.Value(e => e.Title, "Spectre")
.Value(e => e.HireDate, () => Sql.CurrentTimestamp)
.Insert();
or
db.Employee
.Where(e => e.Title == "Spectre")
.Delete();
Then check out this, BLToolkit
You might want to look at http://iqtoolkit.codeplex.com/ Which is very complex and i dont recommend you to build something from scratch.
I just wrote something close to dkons's answer I will add it anyway. Just using fluent interface nothing more.
public class Query<T> where T : class
{
private Dictionary<string, string> _dictionary;
public Query()
{
_dictionary = new Dictionary<string, string>();
}
public Query<T> Eq(Expression<Func<T, string>> property)
{
AddOperator("Eq", property.Name);
return this;
}
public Query<T> StartsWith(Expression<Func<T, string>> property)
{
AddOperator("Sw", property.Name);
return this;
}
public Query<T> Like(Expression<Func<T, string>> property)
{
AddOperator("Like", property.Name);
return this;
}
private void AddOperator(string opName, string prop)
{
_dictionary.Add(opName,prop);
}
public void Run(T t )
{
//Extract props of T by reflection and Build query
}
}
Lets say you have a model like
class Model
{
public string Surname{ get; set; }
public string Name{ get; set; }
}
You can use this as :
static void Main(string[] args)
{
Model m = new Model() {Name = "n", Surname = "s"};
var q = new Query<Model>();
q.Eq(x => x.Name).Like(x=>x.Surname).Run(m);
}

Linq handling variable number of OrderBy

I need to support a variable number of Orderby terms in a Linq (to Entity) statement. That is, my function will accept a list of properties on which the data should be order. The properties can have both ascending or descending sorts. What is the best way to handle constructing the Linq query?
Thanks!
You should be able to do something along these lines:
public IEnumerable<MyType> DoSomething(params Expression<Func<MyType,object>>[] properties)
{
var query = // create LINQ query that returns IQueryable<MyType>
query = query.OrderBy(properties.First());
foreach (var property in properties.Skip(1))
{
query = query.ThenBy(property);
}
}
…
var results = DoSomething(() => x.Age, () => x.Height, () => x.LastName);
You'd need to handle the case where fewer than 2 properties are specified.
Following on from Jay's answer, this can be made into a nice extension method:
public static class EnumerableExtensions
{
public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
params Expression<Func<T, object>>[] expressions)
{
if (expressions.Length == 1)
return enumerable.OrderBy(expressions[0].Compile());
var query = enumerable.OrderBy(expressions[0].Compile());
for (int i = 1; i < expressions.Length;i++)
{
query = query.ThenBy(expressions[i].Compile());
}
return query;
}
}
Usage becomes quite simple, given a test object:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
This is then possible:
var people = new Person[]
{
new Person() {Name = "John", Age = 40},
new Person() {Name = "John", Age = 20},
new Person() {Name = "Agnes", Age = 11}
};
foreach(var per in people.OrderByMany(x => x.Name, x => x.Age))
{
Console.WriteLine("{0} Age={1}",per.Name,per.Age);
}
Output:
Agnes Age=11
John Age=20
John Age=40
UPDATE
You could add another overload of the OrderByMany method to support SortOrder as well, although it gets clunky rather quickly. Personally I'd just go for the syntax
var query = from p
in people
order by Name, Age descending;
However, for the record, in C#4 at least, I would accomplish the overload using an enum & tuple.
public enum SortOrder
{
Ascending,
Descending
}
and the extra overload:
public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
params Tuple<Expression<Func<T, object>>,SortOrder>[] expressions)
{
var query = (expressions[0].Item2 == SortOrder.Ascending)
? enumerable.OrderBy(expressions[0].Item1.Compile())
: enumerable.OrderByDescending(expressions[0].Item1.Compile());
for (int i = 1; i < expressions.Length; i++)
{
query = expressions[i].Item2 == SortOrder.Ascending
? query.ThenBy(expressions[i].Item1.Compile())
: query.ThenByDescending(expressions[i].Item1.Compile());
}
return query;
}
Usage becomes clumsy and hard to read:
foreach (var per in people.OrderByMany(
new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Age, SortOrder.Descending),
new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Name, SortOrder.Ascending)))
{
Console.WriteLine("{0} Age={1}", per.Name, per.Age);
}
To sort by an arbitrary property, you need to build an expression tree to pass to OrderBy.
To sort by an arbitrary number of properties, you need to call ThenBy in a loop.
I like Jamiec's idea but I hate using Tuples because the syntax is ugly. Therefore I built a small class that encapsulates the Tuple and exposes getters for the Item1 and Item2 properties with better variable names.
Also notice that I used a default sort order of ascending so you only need to specify a SortOrder if you want to sort in descending order.
public class SortExpression<T>
{
private Tuple<Expression<Func<T, object>>, SortOrder> tuple;
public SortExpression( Expression<Func<T, object>> expression, SortOrder order =SortOrder.Ascending )
{
tuple = new Tuple<Expression<Func<T,object>>, SortOrder>(expression, order);
}
public Expression<Func<T, object>> Expression {
get { return tuple.Item1; }
}
public SortOrder Order {
get { return tuple.Item2; }
}
}
In my specific application, I have a repository base class which takes an IQueryable and converts it to a ObservableCollection. In that method I use the SortExpression class:
public ObservableCollection<T> GetCollection(params SortExpression<T>[] sortExpressions) {
var list = new ObservableCollection<T>();
var query = FindAll();
if (!sortExpressions.Any()) {
query.ToList().ForEach(list.Add);
return list;
}
var ordered = (sortExpressions[0].Order == SortOrder.Ascending)
? query.OrderBy(sortExpressions[0].Expression.Compile())
: query.OrderByDescending(sortExpressions[0].Expression.Compile());
for (var i = 1; i < sortExpressions.Length; i++) {
ordered = sortExpressions[i].Order == SortOrder.Ascending
? ordered.ThenBy(sortExpressions[i].Expression.Compile())
: ordered.ThenByDescending(sortExpressions[i].Expression.Compile());
}
ordered.ToList().ForEach(list.Add);
return list;
}
Here is the method in use:
var repository = new ContactRepository(UnitOfWork);
return repository.GetCollection(
new SortExpression<Contact>(x => x.FirstName),
new SortExpression<Contact>(x => x.LastName));

Categories