通过字符串生成EF orderby表达式

Generate EF orderby expression by string

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

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改成object.

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

然后可以编译,但是不行

System.NotSupportedException: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.

您可以尝试在泛型方法中转换 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只能是原始类型或枚举类型。

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

请注意,OrderBy 是一种扩展方法,已在 System.Linq.EnumarableSystem.Linq.Queryable 类 中实现。第一个是 and the latter is for . 需要查询的表达式树,以便将其转换为 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 属性 选择器。

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

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

我参考了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

对于那些在 EF Core 中寻找解决方案的人:

Microsoft.EntityFrameworkCore.EF 中有一组函数可用于动态访问和查询编译。您可以使用 EF.Property 方法按 属性 名称甚至阴影属性对可查询进行排序。 示例:

bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
    sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
    descending = true;
}

if (descending)
{
    students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
    students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}

来源:Tutorial: Learn about advanced scenarios - ASP.NET MVC with EF Core