I want to write some API to sort entities on server side (SQLServer) using LINQ2Entities.
I have class which contains expression represents sorting field of entity and sorting direction :
public class SortOption<TEntity>
{
public SortOption(Expression<Func<TEntity, dynamic>> keySelector,
bool ascending = true)
{
KeySelector = keySelector;
Ascending = ascending;
}
public Expression<Func<TEntity, dynamic>> KeySelector { get; private set; }
public bool Ascending { get; private set; }
}
For each of my entities I have class which inherits from above. For example:
public class PostSorting: SortOption<PostEntity>
{
public PostSorting(): base(p => p.Published)
{
}
}
public class PostEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime? Published { get; set; }
public DateTime Modified { get; set; }
public string Title { get; set; }
}
The main goal is to use properties of the SortOption class in my repository's method, which returns entities:
public class Repository<TEntity>
{
public IEnumerable<TEntity> List(SortOption<TEntity> sortOptions)
{
IQueryable<TEntity> query;
if (sortOptions.Ascending)
query = dbSet.OrderBy(sortOptions.KeySelector);
else
query = dbSet.OrderByDescending(sortOptions.KeySelector);
return query;
}
}
*"dbSet" field is System.Data.Entity.DbSet<TEntity>
If I try to sort entities by any property which have type that differs from string type using PostSorting class I get an error like this:
"LINQ to Entities only supports casting EDM primitive or enumeration types.".
For example(sorting by Published field):
"Unable to cast the type 'System.Nullable`1[[System.DateTime, mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' to type
'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types."
or (if I want to order by Modified field)
"Unable to cast the type 'System.DateTime' to type 'System.Object'.
LINQ to Entities only supports casting EDM primitive or enumeration types."
of (if I want to order by Id field)
"Unable to cast the type 'System.Guid' to type 'System.Object'.
LINQ to Entities only supports casting EDM primitive or enumeration types."
I worked on this task for several days but I can not find the answer to solve the problem.
Try using this:
public static class QueryableEx
{
public static IOrderedQueryable<TSource> OrderByEx<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, object>> keySelector)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (keySelector == null)
{
throw new ArgumentNullException("keySelector");
}
// While the return type of keySelector is object, the "type" of
// keySelector.Body is the "real" type *or* it is a
// Convert(body). We rebuild a new Expression with this "correct"
// Body (removing the Convert if present). The return type is
// automatically chosen from the type of the keySelector.Body .
Expression body = keySelector.Body;
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression)body).Operand;
}
LambdaExpression keySelector2 = Expression.Lambda(body, keySelector.Parameters);
Type tkey = keySelector2.ReturnType;
MethodInfo orderbyMethod = (from x in typeof(Queryable).GetMethods()
where x.Name == "OrderBy"
let parameters = x.GetParameters()
where parameters.Length == 2
let generics = x.GetGenericArguments()
where generics.Length == 2
where parameters[0].ParameterType == typeof(IQueryable<>).MakeGenericType(generics[0]) &&
parameters[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(generics[0], generics[1]))
select x).Single();
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(Expression.Call(null, orderbyMethod.MakeGenericMethod(new Type[]
{
typeof(TSource),
tkey
}), new Expression[]
{
source.Expression,
Expression.Quote(keySelector2)
}));
}
}
You'll have to write a OrderByAscending
, but it is the same just with replacing of OrderBy
with OrderByAscending
. The method rewrites the Expression
to use the "right" type.
The code is heavily inspired from the Queryable.OrderBy.