In a generic abstract base class I\'m storing a couple of expressions used for ordering:
public Expression> OrderByString { get;
Amy B's answer is great, and I based my own solution on it. So my point is more of an improvement for what I needed, which I am likely to improve upon in time.
public interface IOrderer<TItem>
{
IOrderedQueryable<TItem> Apply(IQueryable<TItem> source);
}
public class OrderBy<TItem, TType> : IOrderer<TItem>
{
private Expression<Func<TItem, TType>> _orderExpr;
public OrderBy(Expression<Func<TItem, TType>> orderExpr)
{
_orderExpr = orderExpr;
}
public IOrderedQueryable<TItem> Apply(IQueryable<TItem> source)
{
return source.OrderBy(_orderExpr);
}
}
public class ThenBy<TItem, TType> : IOrderer<TItem>
{
private Expression<Func<TItem, TType>> _orderExpr;
public ThenBy(Expression<Func<TItem, TType>> orderExpr)
{
_orderExpr = orderExpr;
}
public IOrderedQueryable<TItem> Apply(IQueryable<TItem> source)
{
return ((IOrderedQueryable<TItem>)source).ThenBy(_orderExpr);
}
}
public class OrderCoordinator<TItem>
{
public List<IOrderer<TItem>> Orders { get; private set; } = new List<IOrderer<TItem>>();
public IQueryable<TItem> ApplyOrder(IQueryable<TItem> source)
{
foreach (IOrderer<TItem> orderer in Orders)
{
source = orderer.Apply(source);
}
return source;
}
public OrderCoordinator<TItem> OrderBy<TValueType>(Expression<Func<TItem, TValueType>> orderByExpression)
{
Orders.Add(new OrderBy<TItem, TValueType>(orderByExpression));
return this;
}
// Can add more sort calls over time
public OrderCoordinator<TItem> ThenBy<TValueType>(Expression<Func<TItem, TValueType>> orderByExpression)
{
Orders.Add(new ThenBy<TItem, TValueType>(orderByExpression));
return this;
}
}
Specify Coordinator with type:
public OrderCoordinator<MyObjectType> OrderCoordinator { get; private set; } = new OrderCoordinator<MyObjectType>();
Specify sort ordering:
OrderCoordinator.OrderBy(e => e.MyStringProperty).ThenBy(e => e.MyIntProperty);
Apply the ordering:
ordered = OrderCoordinator.ApplyOrder(ordered);
Sounds like you want a way to pile up a bunch of Ordering in a list somewhere and apply it. But you can't because each Expression has its own type, which is checked by the compiler when calling OrderBy. You must have those two types when calling OrderBy, but you must have one type to put in the same list.
Hide that second type behind an interface.
public interface IOrderer<T>
{
IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> source);
IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> source);
IOrderedQueryable<T> ApplyThenBy(IOrderedQueryable<T> source);
IOrderedQueryable<T> ApplyThenByDescending(IOrderedQueryable<T> source);
}
public class Orderer<T, U> : IOrderer<T>
{
private Expression<Func<T, U>> _orderExpr;
public Orderer(Expression<Func<T, U>> orderExpr) { _orderExpr = orderExpr; }
public IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> source)
{ return source.OrderBy(_orderExpr); }
public IOrderedQueryable<T> ApplyOrderByDescending(IQueryable<T> source)
{ return source.OrderByDescending(_orderExpr); }
public IOrderedQueryable<T> ApplyThenBy(IOrderedQueryable<T> source)
{ return source.ThenBy(_orderExpr); }
public IOrderedQueryable<T> ApplyThenByDescending(IOrderedQueryable<T> source)
{ return source.ThenByDescending(_orderExpr); }
}
public class OrderCoordinator<T>
{
public List<IOrderer<T>> Orders { get; set; }
public OrderCoordinator() { Orders = new List<IOrderer<T>>(); }
//note, did not return IOrderedQueryable to support ability to return with empty Orders
public IQueryable<T> ApplyOrders(IQueryable<T> source)
{
foreach (IOrderer<T> orderer in Orders)
{
source = orderer.ApplyOrderBy(source);
}
return source;
}
}
public class Customer
{
public string Name { get; set; }
public int FavNumber { get; set; }
}
public class Tester
{
public void Test()
{
OrderCoordinator<Customer> coord = new OrderCoordinator<Customer>();
coord.Orders.Add(new Orderer<Customer, string>(c => c.Name));
coord.Orders.Add(new Orderer<Customer, int>(c => c.FavNumber));
IQueryable<Customer> query = Enumerable.Empty<Customer>().AsQueryable();
query = coord.ApplyOrders(query);
string result = query.Expression.ToString();
}
}
In the debugger:
result = "OrderingDemo.Customer[].OrderBy(c => c.Name).OrderBy(c => c.FavNumber)"
So in your case, instead of this property:
public Expression<Func<T, U>> Order { get; set; }
use this property
public IOrderer<T> Order { get; set; }
This is easy to do if you use the DynamicLinq library found at NuGet.org. This allows you to write queries like;
db.People.Where("Id == 8");
db.People.OrderBy("Created ASC");
That way you can save or pass in your where clauses as strings. No fuss, no muss.
http://nuget.org/List/Packages/DynamicLINQ
Consider using methods instead of properties.
public abstract IEnumerable<T> ApplyOrdering( IEnumerable<T> q );
...
public override IEnumerable<T> ApplyOrdering( IEnumerable<T> q )
{
return q.OrderBy( c => c.CustomerID );
}