按字符串生成EF orderby表达式

时间:2015-08-12 02:20:53

标签: c# linq entity-framework expression-trees

我想通过字符串参数生成表达式,一些代码如:

private Expression<Func<Task, T>> Generate(string orderby)
{
    switch (orderby)
    {
        case "Time":  
            return t => t.Time;
        case "Money":
            return t => t.RewardMoney;
        default:
            return t => t.Id;
    }
}

然后调用它:

_context.Items.OrderBy(Generate("Money"));

但它无法编译!我将T改为对象。

private Expression<Func<Task, object>> Generate(string orderby)

然后它可以编译,但它不起作用。

  

System.NotSupportedException:无法转换类型&#39; System.Int32&#39;键入&#39; System.Object&#39;。 LINQ to Entities仅支持转换EDM原语或枚举类型。

5 个答案:

答案 0 :(得分:34)

使用,您可以提供参数,然后调用OrderBy函数,而不是返回Expression<Func<Task, T>>,然后调用OrderBy

请注意,OrderBy是一种扩展方法,已在System.Linq.EnumarableSystem.Linq.Queryable类中实现。第一个是,后者是需要查询的表达式树才能将其转换为SQL命令。所以我们使用Queryable实现。

可以通过扩展方法完成(解释添加为注释):

public static IOrderedQueryable<TSource> OrderBy<TSource>(
       this IQueryable<TSource> query, string propertyName)
{
    var entityType = typeof(TSource);

    //Create x=>x.PropName
    var propertyInfo = entityType.GetProperty(propertyName);
    ParameterExpression arg = Expression.Parameter(entityType, "x");
    MemberExpression property = Expression.Property(arg, propertyName);
    var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

    //Get System.Linq.Queryable.OrderBy() method.
    var enumarableType = typeof(System.Linq.Queryable);
    var method = enumarableType.GetMethods()
         .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
         .Where(m =>
         {
            var parameters = m.GetParameters().ToList();
            //Put more restriction here to ensure selecting the right overload                
            return parameters.Count == 2;//overload that has 2 parameters
         }).Single();
    //The linq's OrderBy<TSource, TKey> has two generic types, which provided here
    MethodInfo genericMethod = method
         .MakeGenericMethod(entityType, propertyInfo.PropertyType);

    /*Call query.OrderBy(selector), with query and selector: x=> x.PropName
      Note that we pass the selector as Expression to the method and we don't compile it.
      By doing so EF can extract "order by" columns and generate SQL for it.*/
    var newQuery = (IOrderedQueryable<TSource>)genericMethod
         .Invoke(genericMethod, new object[] { query, selector });
    return newQuery;
}

现在你可以调用OrderBy的这个重载,就像任何其他重载一样 例如:

var cheapestItems = _context.Items.OrderBy("Money").Take(10).ToList();

转换为:

SELECT TOP (10)  {coulmn names} FROM  [dbo].[Items] AS [Extent1] 
       ORDER BY [Extent1].[Money] ASC

此方法可用于定义OrderByOrderByDescending方法的所有重载,以使string属性选择器。

答案 1 :(得分:3)

您可以尝试在通用方法中转换Generate方法:

private Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
     switch (orderby)
     {
        case "Time":  
          return t => t.Time;
        case "Money":
          return t => t.RewardMoney;
        default:
         return t => t.Id;
     }
}

因此,如果您调用此方法,则需要指定要按顺序排序的属性的类型:

_context.Items.OrderBy(Generate<decimal>("Money"));

现在请记住TResult只能是基本类型或枚举类型。

答案 2 :(得分:0)

使用通用方法。由于lambda表达式只能分配给强类型的委托或表达式,因此我们必须使用相应的临时文件。然后,我们可以将此温度分配给类型为object的变量。最后,我们可以通过强制转换为结果类型来返回结果。

public Expression<Func<Task, TResult>> Generate<TResult>(string orderby)
{
    object result;
    switch (orderby) {
        case "Time":
            Expression<Func<Task, DateTime>> temp1 = t => t.Time;
            result = temp1;
            break;
        case "Money":
            Expression<Func<Task, decimal>> temp2 = t => t.RewardMoney;
            result = temp2;
            break;
        default:
            Expression<Func<Task, int>> temp3 = t => t.Id;
            result = temp3;
            break;
    }
    return (Expression<Func<Task, TResult>>)result;
}

答案 3 :(得分:0)

public static IQueryable<T> OrderByHelper<T>(this IQueryable<T> source, string propertyName, string sortDirection)
    {

        try
        {
            if (source == null)
            {
                return source;
            }
            if (propertyName == null)
            {
                return source;
            }

            propertyName = propertyName.First().ToString().ToUpper(new CultureInfo("en-US", false)) + propertyName.Substring(1);
            var type = typeof(T);
            var arg = Expression.Parameter(type, "x");

            var propertyInfo = type.GetProperty(propertyName);
            var mExpr = Expression.Property(arg, propertyInfo);
            type = propertyInfo.PropertyType;

            var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
            var lambda = Expression.Lambda(delegateType, mExpr, arg);

            var methodName = !string.IsNullOrEmpty(sortDirection) && sortDirection.ToLower(new CultureInfo("en-US", false)) == "desc" ? "OrderByDescending" : "OrderBy";
            var orderedSource = typeof(Queryable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });

            return (IQueryable<T>)orderedSource;
        }
        catch (Exception)
        {

            return source;
        }
    }

答案 4 :(得分:0)

我在CodePlex中引用了旧的System.Linq.Dynamic codebase,并从实现和调用的角度创建了一个非常简单的版本。当然,它是IQueryable<T>

上的扩展方法
/*
using System;
using System.Linq;
using System.Linq.Expressions;
*/

public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string orderByExpression)
{
    if (string.IsNullOrEmpty(orderByExpression))
        return query;

    string propertyName, orderByMethod;
    string[] strs = orderByExpression.Split(' ');
    propertyName = strs[0];

    if (strs.Length == 1)
        orderByMethod = "OrderBy";
    else
        orderByMethod = strs[1].Equals("DESC", StringComparison.OrdinalIgnoreCase) ? "OrderByDescending" : "OrderBy";

    ParameterExpression pe = Expression.Parameter(query.ElementType);
    MemberExpression me = Expression.Property(pe, propertyName);

    MethodCallExpression orderByCall = Expression.Call(typeof(Queryable), orderByMethod, new Type[] { query.ElementType, me.Type }, query.Expression
        , Expression.Quote(Expression.Lambda(me, pe)));

    return query.Provider.CreateQuery(orderByCall) as IQueryable<T>;
}

以下是如何使用它的示例,已针对Entity Framework Core 3进行了测试:

IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName ASC"); // ORDER BY FirstName
IQueryable<Person> query = dbContext.People;
query = query.OrderBy("FirstName DESC"); // ORDER BY FirstName DESC