返回Func <iqueryable <level>,IOrderedQueryable <level>&gt;在运行时找到的属性排序

时间:2015-12-17 19:26:14

标签: c# linq reflection lambda expression

我有以下方法。它返回由我的存储库Get方法

调用的表达式
  public Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByExpression()
    {
        if (request == null)
        {
            request = new OrderByRequest
            {
                IsAscending = true,  PropertyName = "Name"  // CreatedDate , LevelNo etc
            };
        }
        if (string.IsNullOrWhiteSpace(request.PropertyName))
        {
            request.PropertyName = "Name";
        }
        Type entityType = typeof(Level);
        ParameterExpression parameterExpression = Expression.Parameter(entityType, "x");
        PropertyInfo propertyInfo = entityType.GetProperty(request.PropertyName);
        Expression<Func<Level, object>> sortExpression =
            Expression.Lambda<Func<Level, object>>(
                Expression.Convert(Expression.Property(parameterExpression, request.PropertyName),
                    Type.GetType(propertyInfo.PropertyType.FullName)), parameterExpression);

        Func<IQueryable<Level>, IOrderedQueryable<Level>> expression = request.IsAscending
            ? (Func<IQueryable<Level>, IOrderedQueryable<Level>>)(x => x.OrderBy(sortExpression))
            : (x => x.OrderByDescending(sortExpression));
        return expression;
    }

存储库调用如下(为清晰起见,删除了不必要的代码):

public virtual IQueryable<TEntity> Get(
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
{
     var query = DbContext.Set<TEntity>().AsQueryable();
      if (orderBy != null)
                {
                    query = orderBy(query);
                }
}

上述方法适用于Level类的字符串类型的属性。但对于其他类型(如Integer / DateTime等),它不起作用并抛出错误

  

表达类型&#39; System.Int32&#39;不能用于返回类型   &#39; System.Object的&#39;

我想让这个方法成为一个通用的OrderByExpression提供程序,它将在运行时获取属性名称(此名称将来自客户端),所以    它可以使用该给定对象的任何属性。有可能吗?

2 个答案:

答案 0 :(得分:1)

OrderBy的声明如下

public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, TKey>> keySelector
)

如您所见,还有第二个泛型参数TKey,当选择器表示属性时,属性的类型。您的代码的问题在于您假设每个属性都可以由Func<TSource, object>表示,这通常是不正确的。

这是一个通用功能,可以正确地完成您要完成的任务

public static class QueryableUtils
{
    public static Func<IQueryable<TSource>, IOrderedQueryable<TSource>> OrderByFunc<TSource>(string propertyName, bool ascending = true)
    {
        var source = Expression.Parameter(typeof(IQueryable<TSource>), "source");
        var item = Expression.Parameter(typeof(TSource), "item");
        var member = Expression.Property(item, propertyName);
        var selector = Expression.Quote(Expression.Lambda(member, item));
        var body = Expression.Call(
            typeof(Queryable), ascending ? "OrderBy" : "OrderByDescending",
            new Type[] { item.Type, member.Type },
            source, selector);
        var expr = Expression.Lambda<Func<IQueryable<TSource>, IOrderedQueryable<TSource>>>(body, source);
        var func = expr.Compile();
        return func;
    }
}

在你的情况下使用它将是这样的

public Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByExpression()
{
    if (request == null)
    {
        request = new OrderByRequest
        {
            IsAscending = true,  PropertyName = "Name"  // CreatedDate , LevelNo etc
        };
    }
    if (string.IsNullOrWhiteSpace(request.PropertyName))
    {
        request.PropertyName = "Name";
    }
    return QueryableUtils.OrderByFunc<Level>(request.PropertyName, request.IsAscending);
}

答案 1 :(得分:0)

我找到了另一种方式。

public  Func<IQueryable<Level>, IOrderedQueryable<Level>> GetOrderByFunc()
        {
            if (request == null)
            {
                request = new OrderByRequest
                {
                    IsAscending = true,
                    PropertyName = "Name"  // CreatedDate , LevelNo etc
                };
            }
            if (string.IsNullOrWhiteSpace(request.PropertyName))
            {
                request.PropertyName = "Name";
            }
            Tuple<Expression, Type> selector = GetSelector(new List<string>() {request.PropertyName});
            Type type = selector.Item2;
            Type[] argumentTypes = new[] { typeof(Level), type };

            var orderByMethod = typeof(Queryable).GetMethods()
                .First(method => method.Name == "OrderBy"
                    && method.GetParameters().Count() == 2)
                    .MakeGenericMethod(argumentTypes);
            var orderByDescMethod = typeof(Queryable).GetMethods()
                .First(method => method.Name == "OrderByDescending"
                    && method.GetParameters().Count() == 2)
                    .MakeGenericMethod(argumentTypes);

            if (request.IsAscending)
                return query => (IOrderedQueryable<Level>)
                    orderByMethod.Invoke(null, new object[] {query, selector.Item1});
            else
                return query => (IOrderedQueryable<Level>)
                    orderByDescMethod.Invoke(null, new object[] {query, selector.Item1});
        }

        private static Tuple<Expression, Type> GetSelector(IEnumerable<string> propertyNames)
        {
            var parameter = Expression.Parameter(typeof(Level));
            Expression body = parameter;

            foreach (var property in propertyNames)
            {
                body = Expression.Property(body,
                    body.Type.GetProperty(property));
            }

            return Tuple.Create(Expression.Lambda(body, parameter) as Expression
                , body.Type);
        }