动态创建LINQ到实体OrderBy表达式

时间:2010-05-15 20:04:12

标签: c# linq-to-entities

我正在尝试动态添加orderby表达式。但是当执行下面的查询时,我得到以下异常:

  

System.NotSupportedException:无法使用   创建一个常量值类型   '闭合类型'。只有原始类型   ('如Int32,String和Guid')   在这种情况下得到支持。

奇怪的是,我只是查询那些原始类型。

string sortBy = HttpContext.Current.Request.QueryString["sidx"];
ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting");
Expression orderByProperty = Expression.Property(prm, sortBy);

// get the paged records
IQueryable<PostingListItemDto> query =
   (from posting in be.buskerPosting
    where posting.buskerAccount.cmsMember.nodeId == m.Id
    orderby orderByProperty
    //orderby posting.Created 
    select new PostingListItemDto { Set = posting }).Skip<PostingListItemDto>((page -   1) * pageSize).Take<PostingListItemDto>(pageSize);

希望有人可以对此有所了解!

2 个答案:

答案 0 :(得分:44)

由于它们的翻译方式,你基本上不能使用这样的查询表达式。但是,可以使用扩展方法明确地执行此操作:

string sortBy = HttpContext.Current.Request.QueryString["sidx"];
ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting");
Expression orderByProperty = Expression.Property(prm, sortBy);

// get the paged records
IQueryable<PostingListItemDto> query = be.buskerPosting
    .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
    .OrderBy(orderByExpression)
    .Select(posting => new PostingListItemDto { Set = posting })
    .Skip<PostingListItemDto>((page -   1) * pageSize)
    .Take<PostingListItemDto>(pageSize);

棘手的一点是获得正确的表达式树类型 - 它将出现在编辑中:)

编辑:由于各种原因,编辑会有所延迟。基本上,您可能需要使用反射调用泛型方法,因为Queryable.OrderBy需要通用Expression<Func<TSource, TKey>>,虽然看起来您在编译时知道类型,但您可以不知道密钥类型。如果你知道它总是按(例如)int排序,你可以使用:

Expression orderByProperty = Expression.Property(prm, sortBy);
var orderByExpression = Expression.Lambda<Func<buskerPosting, int>>
    (orderByProperty, new[] { prm });
编辑:好的,看起来我毕竟还有时间。以下是使用反射调用OrderBy的简短示例:

using System;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;

public class Test
{
    static void Main()
    {
        string[] names = { "Jon", "Holly", "Tom", "Robin", "Will" };
        var query = names.AsQueryable();
        query = CallOrderBy(query, "Length");
        foreach (var name in query)
        {
            Console.WriteLine(name);
        }
    }

    private static readonly MethodInfo OrderByMethod =
        typeof(Queryable).GetMethods()
            .Where(method => method.Name == "OrderBy")
            .Where(method => method.GetParameters().Length == 2)
            .Single();

    public static IQueryable<TSource> CallOrderBy<TSource>
        (IQueryable<TSource> source, string propertyName)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
        Expression orderByProperty = Expression.Property(parameter, propertyName);

        LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
        Console.WriteLine(lambda);
        MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
            (new[] { typeof(TSource), orderByProperty.Type });
        object ret = genericMethod.Invoke(null, new object[] {source, lambda});
        return (IQueryable<TSource>) ret;
    }
}

您可以轻松地将CallOrderBy重构为扩展方法(例如OrderByProperty),如下所示:

public static class ReflectionQueryable
{
    private static readonly MethodInfo OrderByMethod =
        typeof(Queryable).GetMethods()
            .Where(method => method.Name == "OrderBy")
            .Where(method => method.GetParameters().Length == 2)
            .Single();

    public static IQueryable<TSource> OrderByProperty<TSource>
        (this IQueryable<TSource> source, string propertyName)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
        Expression orderByProperty = Expression.Property(parameter, propertyName);

        LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
        Console.WriteLine(lambda);
        MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
            (new[] { typeof(TSource), orderByProperty.Type });
        object ret = genericMethod.Invoke(null, new object[] {source, lambda});
        return (IQueryable<TSource>) ret;
    }    
}

您的原始代码将变为:

string sortBy = HttpContext.Current.Request.QueryString["sidx"];
// get the paged records
IQueryable<PostingListItemDto> query = be.buskerPosting
    .Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
    .OrderByProperty(sortBy)
    .Select(posting => new PostingListItemDto { Set = posting })
    .Skip<PostingListItemDto>((page -   1) * pageSize)
    .Take<PostingListItemDto>(pageSize);

(对于涉及水平滚动条的格式化道歉......如果有人关心,我会稍后重新格式化。如果你有足够的代表,你可以为我做这件事;)

答案 1 :(得分:0)

我想用Jon的上述答案作为起点分享我的实现。在这种情况下,不是通过来自表示层的字符串属性名称进行排序(因为此问题的标题不是特定于此),我正构建一个实体框架数据层并希望允许其使用者指定顺序作为lambda表达式的属性。 I.E.我希望能够使用"sidx",而不是传递p => p.sidx。我还希望能够按属性传递无限数量的订单,并能够指定升序或降序。

我的方法可以接受类型为Expression<Func<T, object>>的lambda表达式。这让我称之为我想要的方式,但问题是,除非第二个泛型参数是强类型的,否则Entity Framework无法将表达式转换为SQL。 OrderBy扩展方法需要两个通用参数:T - 属性所属的类型,以及TKey - 属性返回的类型。因此,第一步是修改Jon的示例以将给定的Expression<Func<T, object>>转换为Expression<Func<T, Tkey>>(一旦我们在查询的上下文中工作,我们就可以确定类型TKey):

internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, Expression<Func<T, object>> sortExp)
{
    //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
    //in order for Entity Framework to be able to translate it to SQL
    MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExp.Body) as MemberExpression;
    ParameterExpression sourceParam = sortExp.Parameters[0];

    LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

    MethodInfo orderByMethod = OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

    //Call OrderBy or OrderByDescending on the source IQueryable<T>
    return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
}

正如我所提到的,我希望通过键选择器接受无限数量的顺序,并且还能够指定升序或降序方向,因此我为Expression<Func<T, object>>创建了一个包装类,我将其命名为DynamicSortExpression:< / p>

public class DynamicSortExpression<T>
{
    /// <summary>
    /// Creates a new ascending DynamicSortExpression 
    /// </summary>
    /// <param name="keySelector">A MemberExpression identifying the property to sort on</param>
    public DynamicSortExpression(Expression<Func<T, object>> keySelector) : this(keySelector, false)
    {
    }

    public DynamicSortExpression(Expression<Func<T, object>> keySelector, bool descending)
    {
        this.KeySelector = keySelector;
        this.Desc = descending;
    }

    /// <summary>
    /// Gets the expression that selects the property of T to sort on
    /// </summary>
    public Expression<Func<T, object>> KeySelector { get; }

    /// <summary>
    /// Gets sort expression is in ascending or descending order
    /// </summary>
    public bool Desc { get; }
}

然后我更新了扩展方法以接受此类型并为OrderBy创建了一个接收List<DynamicSortExpression<T>>的重载,并使用扩展方法将它们逐个添加到查询中。这是最终结果:

public static class Extensions
{
    private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods()
                                                        .Where(method => method.Name == "OrderBy")
                                                        .Where(method => method.GetParameters().Length == 2)
                                                        .Single();

    private static readonly MethodInfo OrderByDescMethod = typeof(Queryable).GetMethods()
                                                            .Where(method => method.Name == "OrderByDescending")
                                                            .Where(method => method.GetParameters().Length == 2)
                                                            .Single();

    private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods()
                                                    .Where(method => method.Name == "ThenBy")
                                                    .Where(method => method.GetParameters().Length == 2)
                                                    .Single();

    private static readonly MethodInfo ThenByDescMethod = typeof(Queryable).GetMethods()
                                                    .Where(method => method.Name == "ThenByDescending")
                                                    .Where(method => method.GetParameters().Length == 2)
                                                    .Single();

    internal static IQueryable<T> OrderBy<T>(this IQueryable<T> sourceQuery, List<DynamicSortExpression<T>> orderBy)
    {
        bool isFirst = true;
        foreach (var sortExpression in orderBy)
        {
            if (isFirst)
            {
                sourceQuery = sourceQuery.OrderByDynamic(sortExpression);
                isFirst = false;
            }
            else
                sourceQuery = sourceQuery.ThenByDynamic(sortExpression);
        }

        return sourceQuery;
    }

    internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
    {
        //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
        //in order for Entity Framework to be able to translate it to SQL
        MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
        ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];

        LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

        MethodInfo orderByMethod = sortExpression.Desc ?
                                        OrderByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
                                        OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

        //Call OrderBy or OrderByDescending on the source IQueryable<T>
        return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
    }

    internal static IQueryable<T> ThenByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
    {
        //We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
        //in order for Entity Framework to be able to translate it to SQL
        Expression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
        ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];

        LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });

        MethodInfo orderByMethod = sortExpression.Desc ?
                                        ThenByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
                                        ThenByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });

        //Call OrderBy or OrderByDescending on the source IQueryable<T>
        return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
    }
}

现在我的数据层可以有一个类似List<T> GetList(Expression<Func<T, bool>> where, params DynamicSortExpression<T>[] orderBy)的方法,可以像

一样调用
new MyClass<Person>().GetList(p => p.FirstName == "Billy", //where clause
                              new DynamicSortExpression<T>(p => p.FirstName),
                              new DynamicSortExpression<T>(p => p.LastName, true));

我从EntityFramework源代码中抽出RemoveConvert方法,以递归方式从MemberExpression中删除转换调用:

internal static Expression RemoveConvert(Expression expression)
{
    System.Diagnostics.Debug.Assert(expression != null);

    while ((expression != null)
            && (expression.NodeType == ExpressionType.Convert
                || expression.NodeType == ExpressionType.ConvertChecked))
    {
        expression = RemoveConvert(((UnaryExpression)expression).Operand);
    }

    return expression;
}

我希望这有用!谢谢Jon!