按字符串生成EF orderby表达式

问题描述 投票:15回答:3

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

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:无法将类型“System.Int32”强制转换为“System.Object”类型。 LINQ to Entities仅支持转换EDM原语或枚举类型。

c# linq entity-framework expression-trees
3个回答
36
投票

使用你可以提供参数,然后调用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属性选择器。


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只能是原始类型或枚举类型。


0
投票

使用通用方法。由于lambda表达式只能分配给强类型的委托或表达式,因此我们必须使用相应的temp。然后我们可以将此临时值分配给类型为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;
}
© www.soinside.com 2019 - 2024. All rights reserved.