How to develop a general method for linq statements? - c#

I need to create a general routine in visual studio to get some parameters as input and return a list resulted from a repository. I am using Linq. But I am not sure how to develop this function and neither what key words I can use and find some resources.
This is a sample code that already is used in my program:
var lstReceiptDetails = Repository<TransactionDetail>()
.Where(current => current.HeaderId == headerId)
.OrderBy(current => current.DocumentRow)
.ToList();
I need to change the above linq statement to something like the following pseudocode:
private List<> GetQuery(repositoryName, conditionFieldName, orderFieldName )
{
var lstResult = Repository<repositiryName>()
.Where(current => current.ConditionFieldName == conditionFieldName)
.OrderBy(current => current.orderFieldName)
.ToList();
Return(lstResult);
}
Any help is appreciate.
Maryam

I think the closest way you can get is by using the following example below. I've tried a several ways to do this, but it would harm the usability and the readability. This is a compromise between code duplication and readability.
A sample POCO object:
class TransactionDetail
{
public DateTime DateProcessed { get; set; }
public string AccountName { get; set; }
}
The repositories:
abstract class GenericRepository<T>
{
public List<T> GetQuery<TKey>(
Func<T, bool> conditionFieldName,
Func<T, TKey> orderFieldName)
{
var lstResult = Repository()
.Where(conditionFieldName)
.OrderBy(orderFieldName)
.ToList();
return lstResult;
}
private IEnumerable<T> Repository()
{
throw new NotImplementedException();
}
}
class TransactionDetailRepository : GenericRepository<TransactionDetail>
{
}
And caller-side:
var repository = new TransactionDetailRepository();
var transactions = repository.GetQuery(
x => x.AccountName == "Foo Bar",
x => x.DateProcessed);
Argument checks should still be implemented properly though.
If this piece of code should be used in EntityFramework or Linq-to-SQL, parameters should be wrapped in Expression<T> such that, for example: Func<T, bool> becomes Expression<Func<T, bool>>

You can try to use the LINQ Dynamic Query Library that take string arguments instead of type-safe language operators.
Short example:
var result = Repository<repositoryName>().
Where("Id = 1").
Select("new(Id, Name)");
More information here: http://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library

Related

Use LinqKit PredicateBuilder for related model (EF Core)

I want to use LinqKit's PredicateBuilder and pass the predicate into .Any method for related model.
So I want to build a predicate:
var castCondition = PredicateBuilder.New<CastInfo>(true);
if (movies != null && movies.Length > 0)
{
castCondition = castCondition.And(c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
castCondition = castCondition.And(c => c.RoleId == roleType);
}
And then use it to filter model that has relation to model in predicate:
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
But this causes a System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
I saw similar question and answer there suggests to use .Compile. Or one more question that build an extra predicate.
So I tried to use extra predicate
var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);
Or use compile directly
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));
But I have an error about Compile: System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'
So is it possible to convert the result from PredicateBuilder to pass into Any?
Note: I was able to build the desired behavior combining expressions, but I don't like that I need extra variables.
System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true);
if (movies != null && movies.Length > 0)
{
castExpression = (c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
var existingExpression = castExpression;
castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType;
}
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
So I assume I just miss something about builder.
Update about versions: I use dotnet core 2.0 and LinqKit.Microsoft.EntityFrameworkCore 1.1.10
Looking at the code, one will assume that the type of castCondition variable is Expression<Func<CastInfo, bool>> (as it was in earlier versions of PredicateBuilder).
But if that was the case, then n.CastInfo.Any(castCondition) should not even compile (assuming CastInfo is a collection navigation property, so the compiler will hit Enumerable.Any which expects Func<CastInfo, bool>, not Expression<Func<CastInfo, bool>>). So what's going on here?
In my opinion, this is a good example of C# implicit operator abuse. The PredicateBuilder.New<T> method actually returns a class called ExpressionStarter<T>, which has many methods emulating Expression, but more importantly, has implicit conversion to Expression<Func<T, bool>> and Func<CastInfo, bool>. The later allows that class to be used for top level Enumerable / Queryable methods as replacement of the respective lambda func/expression. However, it also prevents the compile time error when used inside the expression tree as in your case - the complier emits something like n.CastInfo.Any((Func<CastInfo, bool>)castCondition) which of course causes exception at runtime.
The whole idea of LinqKit AsExpandable method is to allow "invoking" expressions via custom Invoke extension method, which then is "expanded" in the expression tree. So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>, the intended usage is:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
But now this doesn't compile because of the reason explained earlier. So you have to convert it first to Expression<Func<T, bool> outside of the query:
Expression<Func<CastInfo, bool>> castPredicate = castCondition;
and then use
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
or
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
To let compiler infer the expression type, I would create a custom extension method like this:
using System;
using System.Linq.Expressions;
namespace LinqKit
{
public static class Extensions
{
public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
}
}
and then simply use
var castPredicate = castCondition.ToExpression();
It still has to be done outside of the query, i.e. the following does not work:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
It may not be exactly related to the original question, but considering the following model :
public Class Music
{
public int Id { get; set; }
public List<Genre> Genres { get; set; }
}
public Class Genre
{
public int Id { get; set; }
public string Title { get; set; }
}
List<string> genresToFind = new() {"Pop", "Rap", "Classical"};
If you are trying to find all Musics that their genres exist in genresToFind list, here's what you can do:
Create PredicateBuilder expressions chain on Genre model :
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
Then execute your query like this :
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression() is a generic extension method that we've created to convert ExpressionStarter<Genre> type to Expression<Func<Genre, bool>> :
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
Also, you'll need LinqKit.Microsoft.EntityFrameworkCore package for efcore.

LINQ filter combining exact matches like SQL IN and StartsWith matches

I'm having product entity:
public class Product : DomainBase
{
public virtual string Name { get; set; }
}
And there should be option to select products by filter, which contains an array of names, like:
public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var session = Database.OpenSession())
{
var products = session.Query<Product>();
var result = products.Where(product => names.Any(name => product.Name.Contains(name)));
return result.ToList();
}
}
but it throws
System.NotSupportedException: Specified method is not supported.
What is right approach, to accomplish such filtering?
Without knowing more about what database you're connecting to or what library (is it RavenDB.. having done a quick Google?) then it's hard to be completely sure what the problem is.
However, what I think is happening is that you are giving an expression to the IQueryable "Where" extension method and the library is trying to turn that into search criteria to run against the db.. and failing because "Any" is not supported in nested criteria like that (again, I'm guessing).
The LINQ expressions that may or may not be translated into the database language (eg. SQL) vary by the library that performs the translation and vary by the database being talked to.
For example, the following (which is basically what you want to do) works fine with Entity Framework:
private static void Test(IEnumerable<string> names)
{
using (var context = new NORTHWNDEntities())
{
foreach (var product in context.Products.Where(product => names.Any(name => product.ProductName.Contains(name))))
{
Console.WriteLine(product.ProductName);
}
}
Console.ReadLine();
}
One easy option for you is to change your code to
public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var session = Database.OpenSession())
{
var products = session.Query<Product>();
return result = products.ToList().Where(product => names.Any(name => product.Name.Contains(name)));
}
}
This should work.. however, it will get all Products from the database and perform the filtering in-memory. This is less efficient than getting the database to perform the search.
An alternative would be to generate an "Expression<Func<Product, bool>>" filter yourself that is easier for the library that you're using to translate. If, instead, of a nested "Any" criteria, you could generate a simple set of "OR" name checks then there is a better change of it working. The following will achieve that - but it's quite a lot of code. If this is something that you need to do in several places then some of the code could be made more general and reused.
private static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var context = new NORTHWNDEntities())
{
return context.Products.Where(GetCombinedOrFilter(names)).ToList();
}
}
private static Expression<Func<Product, bool>> GetCombinedOrFilter(IEnumerable<string> names)
{
var filter = GetNameFilter(names.First());
foreach (var name in names.Skip(1))
filter = CombineFiltersAsOr(filter, GetNameFilter(name));
return filter;
}
private static Expression<Func<Product, bool>> GetNameFilter(string name)
{
return product => product.ProductName.Contains(name);
}
private static Expression<Func<Product, bool>> CombineFiltersAsOr(Expression<Func<Product, bool>> x, Expression<Func<Product, bool>> y)
{
// Combine two separate expressions into one by combining as "Or". In order for this to work, instead of there being a parameter
// for each expression, the parameter from the first expression must be shared between them both (otherwise things will go awry
// when this is translated into a database query) - this is why ParameterRebinder.ReplaceParameters is required.
var expressionParameter = x.Parameters.Single();
return Expression.Lambda<Func<Product, bool>>(
Expression.Or(x.Body, ParameterRebinder.ReplaceParameters(y.Body, toReplace: y.Parameters.Single(), replaceWith: expressionParameter)),
expressionParameter
);
}
// Borrowed and tweaked from https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/
public sealed class ParameterRebinder : ExpressionVisitor
{
public static Expression ReplaceParameters(Expression expression, ParameterExpression toReplace, ParameterExpression replaceWith)
{
return new ParameterRebinder(toReplace, replaceWith).Visit(expression);
}
private readonly ParameterExpression _toReplace, _replaceWith;
private ParameterRebinder(ParameterExpression toReplace, ParameterExpression replaceWith)
{
_toReplace = toReplace;
_replaceWith = replaceWith;
}
protected override Expression VisitParameter(ParameterExpression p)
{
if (p == _toReplace)
p = _replaceWith;
return base.VisitParameter(p);
}
}
Update: I didn't notice your nhibernate tag - whoops! Using the criteria combining methods that nhibernate has is probably easier than all this.. :) I would have commented on your answer rather than updating my own but I haven't got the requisite 50 rep yet..
You are trying to mix both kinds of conditions and applying IEnumerable methods on string properties.
Your query should look like this:
var result = products.Where(product => names.Contains(product.Name));
to find exact matches.
For a combination of exact matches and StartsWith it should look like this:
var results = products.Where(product => (names.Contains(product.Name) || names.Any(name => name.StartsWith(product.Name))));
As I did a dive into NHibenrate documentation, it contains CriteriaAPI, so I came up to this
using (var session = Database.OpenSession())
{
var products = session.CreateCriteria<Product>();
if (names == null)
{
return products.List<Product>();
}
var orClause = Expression.Disjunction();
foreach (var name in names)
{
orClause.Add(Expression.Like(nameof(Product.Name), name, MatchMode.Start));
}
products.Add(orClause);
return products.List<Product>();
}

How should I share filtering logic between multiple LINQ-where clauses?

Let A be a class whith some property Hello. I would like to filter collections of A instances by this property in many places. Thus I'd make somewhere a static member of type Expression<Func<A, bool>> which denotes the filtering predicate and I use it in all places where I do a filtering. (This predicate would be transformed by an ORM to some concrete DB-specific expression.)
Next. There exists a class B with property of type A. I would like to filter collection of B instances by the same logic as was used in the first case (by property Hello of class A).
Question. What is the most correct way to implement this and reduce code duplication?
My suggestion. Add three things: 1) interface IWithA with property of type A, 2) class WithA<T> implementing this interface IWithA and providing property of type T, 3) some static property of type Expression<Func<IWithA, bool>> implementing the filtering logic. Demo code is following.
public static void Main() {
var listOfAs = new List<A>().AsQueryable();
var query0 = listOfAs
.Select(a => new WithA<A> {
A = a,
Smth = a,
})
.Where(Filter);
var listOfBs = new List<B>().AsQueryable();
var query1 = listOfBs
.Select(b => new WithA<B> {
A = b.A,
Smth = b,
})
.Where(Filter);
}
private class A {
public int Hello { get; set; }
}
private class B {
public A A { get; set; }
}
private interface IWithA {
A A { get; set; }
}
private class WithA<T> : IWithA {
public A A { get; set; }
public T Smth { get; set; }
}
private static readonly Expression<Func<IWithA, bool>> Filter = a => a.A.Hello > 0;
The problem with this approach: 1) one must always make Select(x => new WithA<X> { ... }), 2) the ORM may not support this.
About the answer. I am satisfied with the accepted answer (by Ivan Stoev). I think it is the best possible approach. Also it is helpful to look at the suggestion from Mihail Stancescu (see it in comments to the question). Still I do not understand the answer from user853710; may be it is useful also.
I would create and use a helper function that converts the original Expression<A, bool> to Expression<B, bool> using the System.Linq.Expressions like this
public static class ExpressionUtils
{
public static Expression<Func<TTarget, bool>> ConvertTo<TSource, TTarget>(this Expression<Func<TSource, bool>> source, Expression<Func<TTarget, TSource>> sourceSelector)
{
var body = new ParameterExpressionReplacer { source = source.Parameters[0], target = sourceSelector.Body }.Visit(source.Body);
var lambda = Expression.Lambda<Func<TTarget, bool>>(body, sourceSelector.Parameters);
return lambda;
}
class ParameterExpressionReplacer : ExpressionVisitor
{
public ParameterExpression source;
public Expression target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == source ? target : base.VisitParameter(node);
}
}
}
Sample usage
Expression<Func<A, bool>> filterA = item => item.Hello == 2; // The original logic
var filterB = filterA.ConvertTo((B b) => b.A);
This approach doesn't require any changes to your entity model. Of course you can cache the filters in a static properties of the respective classes if you wish, but the principle is still write the logic in one place and then just use convert in other places.
I would suggest you not to reinvent the wheel. Take a look at the Library LinqKit
The Predicatebuilder is what you need and it gives you much more. Albahari (The developer) has amazing products and libraries for sharing and give you much fun using threm

How to check for nulls in a deep lambda expression? [duplicate]

This question already has answers here:
Possible pitfalls of using this (extension method based) shorthand
(11 answers)
Closed 9 years ago.
How can I check for nulls in a deep lamda expression?
Say for example I have a class structure that was nested several layers deep, and I wanted to execute the following lambda:
x => x.Two.Three.Four.Foo
I want it to return null if Two, Three, or Four were null, rather than throwing a System.NullReferenceException.
public class Tests
{
// This test will succeed
[Fact]
public void ReturnsValueWhenClass2NotNull()
{
var one = new One();
one.Two = new Two();
one.Two.Three = new Three();
one.Two.Three.Four = new Four();
one.Two.Three.Four.Foo = "blah";
var result = GetValue(one, x => x.Two.Three.Four.Foo);
Assert.Equal("blah", result);
}
// This test will fail
[Fact]
public void ReturnsNullWhenClass2IsNull()
{
var one = new One();
var result = GetValue(one, x => x.Two.Three.Four.Foo);
Assert.Equal(null, result);
}
private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
{
var func = expression.Compile();
var value = func(model);
return value;
}
public class One
{
public Two Two { get; set; }
}
public class Two
{
public Three Three { get; set; }
}
public class Three
{
public Four Four { get; set; }
}
public class Four
{
public string Foo { get; set; }
public string Bar { get; set; }
}
}
UPDATE:
One solution would be to catch the NullReferenceException like this:
private TResult GetValue<TModel, TResult>(TModel model, Expression<Func<TModel, TResult>> expression)
{
TResult value;
try
{
var func = expression.Compile();
value = func(model);
}
catch (NullReferenceException)
{
value = default(TResult);
}
return value;
}
But I hate to incur the expense of catching an exception that is not, in my mind, exceptional. I expect this to be the case quite often in my domain.
UPDATE 2:
Another solution would be modify the property getters like this:
public class One
{
private Two two;
public Two Two
{
get
{
return two ?? new Two();
}
set
{
two = value;
}
}
}
Which is mostly ok for my domain, but there are times when I really to expect a property to return null. I checked the answer from Josh E as helpful since it comes pretty close to what I need in some cases.
You could do this with a generic helper extension method, something like:
public static class Get {
public static T IfNotNull<T, U>(this U item, Func<U, T> lambda) where U: class {
if (item == null) {
return default(T);
}
return lambda(item);
}
}
var one = new One();
string fooIfNotNull = one.IfNotNull(x => x.Two).IfNotNull(x => x.Three).IfNotNull(x => x.Four).IfNotNull(x => x.Foo);
You can't do that in a concise way. You can either make the lambda multiple lines, or use nested ternary operators:
var result = GetValue(one, x => x.Two == null ? null :
x.Two.Three == null ? null :
x.Two.Three.Four == null ? null :
x.Two.Three.Four.Foo;
Ugly, I know.
Doing this concisely requires an as-yet-unimplemented operator. We considered adding an operator ".?" to C# 4.0 which would have your desired semantics, but unfortunately it did not fit into our budget. We'll consider it for a hypothetical future version of the language.
You can now do using the Maybe project on codeplex.
Syntax is:
string result = One.Maybe(o => o.Two.Three.Four.Foo);
string cityName = Employee.Maybe(e => e.Person.Address.CityName);
I've written an extension method which enables you to do this:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo);
It uses Expression Trees to build a nested conditional checking for nulls at each node before returning the expression value; the created expression tree is compiled to a Func and cached, so subsequent uses of the same call should run at almost native speed.
You can also pass in a default value to return if you like:
blah.GetValueOrDefault(x => x.Two.Three.Four.Foo, Foo.Empty);
I've written a blog about it here.
I'm not skilled in c#, but maybe there's some way to implement the "andand" pattern from ruby that solves exactly this problem without polluting the implementation.
The concept is also known as the Maybe Monad in Haskell.
The title of this article seems promising.
Always initialize your properties before using them. Add a constructor to class One, Two, Three and Four. In the constructor initialize your properties so they are not null.
You could modify your getters to read something like:
private Two _two;
public Two Two
{
get
{
if (null == _two)
return new Two();
else
return _two;
}
}
I find the coalesce operator useful for this at times. This only helps though if there is a default/null equivalent version of the object you can drop in.
For instance, sometimes when I'm cracking open XML...
IEnumeratable<XElement> sample;
sample.Where(S => (S.Attribute["name"] ?? new XAttribute("name","")).Value.StartsWith("Hello"))...
Depending on how the default objects are retrieved this can be verbose, and the above example is not a great use but you get the idea. For the particular case of reading XML attributes I have an extension method that returns the attribute value or an empty string.
I converted a function that used a lot of if statements to avoid the nulls to the .IFNotNull method for classes that I converted from an XSD that are 4 and 5 levels deep.
Here are a few lines of the converted code:
ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_VALUE = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.VALUE).ToDouble();
ProdYear.PRIOR_CUMULATIVE_CARBON_DIOXIDE_UOM = year.IfNotNull(x => x.PRIOR_CUMULATIVE).IfNotNull(y => y.CARBON_DIOXIDE).IfNotNull(z => z.UOM);
Here are some interesting stats about it:
1) This new method took 3.7409 times longer to run that the variation with the If Statements.
2) I decreased my function line count from 157 to 59.
3) CodeRush from DevExpress has a "Maintenance Complexity" score. When I converted to the Lambda statements, it increased from 984 to 2076, which is theoretically much harder to maintain.

How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree

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 documentation for Supported and Unsupported Methods does not mention it, but you can't "call" Skip on a unordered IQueryable. It will raise the following exception:
System.NotSupportedException: The method 'Skip' is only supported for
sorted input in LINQ to Entities. The method 'OrderBy' must be called before
the method 'Skip'..
I solved it by allowing to define a default sorting via a partial method. But I'm having problems checking if the expression tree indeed contains an OrderBy.
I've reduced the problem to as less code as possible:
public partial class Repository
{
partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery);
public IQueryable<Category> List(int startIndex, int count)
{
IQueryable<Category> query = List();
ProvideDefaultSorting(ref query);
if (!IsSorted(query))
{
query = query.OrderBy(c => c.CategoryID);
}
return query.Skip(startIndex).Take(count);
}
public IQueryable<Category> List(string sortExpression, int startIndex, int count)
{
return List(sortExpression).Skip(startIndex).Take(count);
}
public IQueryable<Category> List(string sortExpression)
{
return AddSortingToTheExpressionTree(List(), sortExpression);
}
public IQueryable<Category> List()
{
NorthwindEntities ent = new NorthwindEntities();
return ent.Categories;
}
private Boolean IsSorted(IQueryable<Category> query)
{
return query is IOrderedQueryable<Category>;
}
}
public partial class Repository
{
partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery)
{
currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting..
}
}
This is not my real implementation!
But my question is, how could I implement the IsSorted method? The problem is that LINQ to Entities query's are always of the type ObjectQuery, which implements IOrderedQueryable.
So how should I make sure an OrderBy method is present in the expression tree? Is the only option to parse the tree?
Update
I've added two other overloads to make clear that it's not about how to add sorting support to the repository, but how to check if the ProvideDefaultSorting partial method has indeed added an OrderBy to the expression tree.
The problem is, the first partial class is generate by a template and the implementation of the second part of the partial class is made by a team member at another time. You can compare it with the way the .NET Entity Framework generates the EntityContext, it allows extension points for other developers. So I want to try to make it robust and not crash when the ProvideDefaultSorting is not implemented correctly.
So maybe the question is more, how can I confirm that the ProvideDefaultSorting did indeed add sorting to the expression tree.
Update 2
The new question was answered, and accepted, I think I should change the title to match the question more. Or should I leave the current title because it will lead people with the same problem to this solution?
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));
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.
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]
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.)

Categories