使用 Entity Framework 的规范模式(按 属性 排序)

Specification pattern using Entity Framework (order by property)

我想写一些 API 来使用 LINQ2Entities 在服务器端 (SQLServer) 对实体进行排序。

我有class,其中包含表示实体排序字段和排序方向的表达式:

    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; }
    }

对于我的每个实体,我都有 class 从上面继承的。例如:

    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; }
    }

主要目标是在我的存储库方法中使用 SortOption class 的属性,returns 个实体:

  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" 字段为 System.Data.Entity.DbSet<TEntity>

如果我尝试使用 PostSorting class 按任何类型不同于字符串类型的 属性 对实体进行排序,我会收到如下错误:

"LINQ to Entities only supports casting EDM primitive or enumeration types.".

例如(按发布字段排序):

 "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."

或(如果我想按修改字段排序)

 "Unable to cast the type 'System.DateTime' to type 'System.Object'.
 LINQ to Entities only supports casting EDM primitive or enumeration types."

of(如果我想按 Id 字段排序)

 "Unable to cast the type 'System.Guid' to type 'System.Object'. 
 LINQ to Entities only supports casting EDM primitive or enumeration types."

我在这个任务上工作了好几天,但找不到解决问题的答案。

试试这个:

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)
        }));
    }
}

你必须写一个OrderByAscending,但是把OrderBy替换成OrderByAscending也是一样的。该方法重写 Expression 以使用 "right" 类型。

代码的灵感来自 Queryable.OrderBy