I\'m using T4 for generating repositories for LINQ to Entities entities.
The repository contains (amongst other things) a List method suitable for paging. The docum
ProvideDefaultSorting(ref query);
if (!IsSorted(query))
{
query = query.OrderBy(c => c.CategoryID);
}
Change to:
//apply a default ordering
query = query.OrderBy(c => c.CategoryID);
//add to the ordering
ProvideDefaultSorting(ref query);
It's not a perfect solution.
It doesn't solve the "filter in the ordering function" problem you've stated. It does solve "I forgot to implement ordering" or "I choose not to order".
I tested this solution in LinqToSql:
public void OrderManyTimes()
{
DataClasses1DataContext myDC = new DataClasses1DataContext();
var query = myDC.Customers.OrderBy(c => c.Field3);
query = query.OrderBy(c => c.Field2);
query = query.OrderBy(c => c.Field1);
Console.WriteLine(myDC.GetCommand(query).CommandText);
}
Generates (note the reverse order of orderings):
SELECT Field1, Field2, Field3
FROM [dbo].[Customers] AS [t0]
ORDER BY [t0].[Field1], [t0].[Field2], [t0].[Field3]
Paging depends on Ordering in a strong way. Why not tightly couple the operations? Here's one way to do that:
Support objects
public interface IOrderByExpression<T>
{
ApplyOrdering(ref IQueryable<T> query);
}
public class OrderByExpression<T, U> : IOrderByExpression<T>
{
public IQueryable<T> ApplyOrderBy(ref IQueryable<T> query)
{
query = query.OrderBy(exp);
}
//TODO OrderByDescending, ThenBy, ThenByDescending methods.
private Expression<Func<T, U>> exp = null;
//TODO bool descending?
public OrderByExpression (Expression<Func<T, U>> myExpression)
{
exp = myExpression;
}
}
The method under discussion:
public IQueryable<Category> List(int startIndex, int count, IOrderByExpression<Category> ordering)
{
NorthwindEntities ent = new NorthwindEntities();
IQueryable<Category> query = ent.Categories;
if (ordering == null)
{
ordering = new OrderByExpression<Category, int>(c => c.CategoryID)
}
ordering.ApplyOrdering(ref query);
return query.Skip(startIndex).Take(count);
}
Some time later, calling the method:
var query = List(20, 20, new OrderByExpression<Category, string>(c => c.CategoryName));
I have implemented a solution that sorts whatever collection by its primary key as the default sort order is not specified. Perhaps that will work for you.
See http://johnkaster.wordpress.com/2011/05/19/a-bug-fix-for-system-linq-dynamic-and-a-solution-for-the-entity-framework-4-skip-problem/ for the discussion and the general-purpose code. (And an incidental bug fix for Dynamic LINQ.)
You can address this in the return type of ProvideDefaultSorting. This code does not build:
public IOrderedQueryable<int> GetOrderedQueryable()
{
IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
return myInts.Where(i => i == 2);
}
This code builds, but is insidious and the coder gets what they deserve.
public IOrderedQueryable<int> GetOrderedQueryable()
{
IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
return myInts.Where(i => i == 2) as IOrderedQueryable<int>;
}
Same story with ref (this does not build):
public void GetOrderedQueryable(ref IOrderedQueryable<int> query)
{
query = query.Where(i => i == 2);
}
I'm afraid it's a bit harder than that. You see, the Entity Framework will, in certain circumstances, silently ignore an OrderBy. So it isn't enough to just look for an OrderBy in the expression tree. The OrderBy has to be in the "right" place, and the definition of the "right" place is an implementation detail of the Entity Framework.
As you may have guessed by now, I'm in the same place as you are; I'm using the entity repository pattern and doing a Take/Skip on the presentation layer. The solution I have used, which is perhaps not ideal, but good enough for what I'm doing, is to not do any ordering until the last possible moment, to ensure that the OrderBy is always the last thing into the expression tree. So any action which is going to do a Take/Skip (directly or indirectly) inserts an OrderBy first. The code is structured such that this can only happen once.
Thanks to David B I've got a the following solution. (I had to add detection for the situation where the partial method was not executed or just returned it's parameter).
public partial class Repository
{
partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery);
public IQueryable<Category> List(int startIndex, int count)
{
NorthwindEntities ent = new NorthwindEntities();
IOrderedQueryable<Category> query = ent.CategorySet;
var oldQuery = query;
ProvideDefaultSorting(ref query);
if (oldQuery.Equals(query)) // the partial method did nothing with the query, or just didn't exist
{
query = query.OrderBy(c => c.CategoryID);
}
return query.Skip(startIndex).Take(count);
}
// the rest..
}
public partial class Repository
{
partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery)
{
currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")).OrderBy(c => c.CategoryName); // compile time forced sotring
}
}
It ensures at compile time that if the partial method is implemented, it should at least keep it an IOrderdQueryable.
And when the partial method is not implemented or just returns its parameter, the query will not be changed, and that will use the fallback sort.