【问题标题】:How to construct Order By Expression dynamically in Entity Framework?如何在实体框架中动态构造 Order By Expression?
【发布时间】:2016-01-20 17:19:32
【问题描述】:

我使用了以下方法来构造Order By ExpressionOriginal Source

真的很漂亮。缺点是它只有在属性是 string 类型时才有效。

我怎样才能让它接受不同的属性类型而不为不同的数据类型创建一堆方法

public static bool PropertyExists<T>(string propertyName)
{
    return typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase |
      BindingFlags.Public | BindingFlags.Instance) != null;
}

public static Expression<Func<T, string>> GetPropertyExpression<T>(string propertyName)
{
    if (typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase | 
        BindingFlags.Public | BindingFlags.Instance) == null)
    {
        return null;
    }

    var paramterExpression = Expression.Parameter(typeof(T));

    return (Expression<Func<T, string>>)Expression.Lambda(
        Expression.PropertyOrField(paramterExpression, propertyName), paramterExpression);
}

用法

// orderBy can be either Name or City.
if (QueryHelper.PropertyExists<Club>(orderBy)) 
{ 
   var orderByExpression = QueryHelper.GetPropertyExpression<Club>(orderBy); 
   clubQuery = clubQuery.OrderBy(orderByExpression); 
} 
else 
{ 
   clubQuery = clubQuery.OrderBy(c => c.Id); 
} 

问题

public class Club 
{ 
  public int Id { get; set; } 
  public string Name { get; set; } 
  public string City { get; set; } 
  public DateTime CreateDate { get; set; } <= this won't work
} 

我目前的方法(if 语句太多)

public static Expression<Func<TSource, TKey>> 
    GetPropertyExpression<TSource, TKey>(string propertyName)
{
    if (typeof (TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | 
        BindingFlags.Public | BindingFlags.Instance) == null)
    {
        return null;
    }
    var paramterExpression = Expression.Parameter(typeof (TSource));
    return (Expression<Func<TSource, TKey>>) 
        Expression.Lambda(Expression.PropertyOrField(
           paramterExpression, propertyName), paramterExpression);
}

缺点是每种数据类型都有很多 if 语句。

if (QueryHelper.PropertyExists<Club>(orderBy)) 
{
   if(orderBy == "CreateDate")
   {       
      var orderByExpression = GetPropertyExpression<Club, DateTime>(orderBy);
      ...
   }
   else if(orderBy == "Name" || orderBy == "City")
   {
      var orderByExpression = GetPropertyExpression<Club, string>(orderBy);
      ...
   }
   ...
}
else 
{ 
   clubQuery = clubQuery.OrderBy(c => c.Id); 
} 

【问题讨论】:

  • 所以问题出在var orderByExpression = QueryHelper.GetPropertyExpression&lt;Club&gt;(orderBy);
  • 问题是每种数据类型的重复代码太多。请参考我发布的最后一个代码。对于使用动态 OrderBy 的人来说,这似乎是一个非常普遍的问题;但我在 Google 中找不到任何解决方案。 我愿意接受任何建议或替代方法
  • 我不会传入“CreateDate”或“Name”或“City”之类的,而是传入某种表示要排序的属性的对象。该对象将能够生成排序表达式和其他有用的东西。更多代码,但没有凌乱的 if 丛林。
  • @MikeChristensen 返回的Expression 的类型是什么?
  • @moarboilerplate - 嗯,我想LambdaExpressionExpression&lt;TDelegate&gt; 给你的唯一东西是 CompileUpdate 他可能不需要。

标签: c# entity-framework


【解决方案1】:

我在Jon Skeet 的旧答案的帮助下找到了解决方案。

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

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

    public static bool PropertyExists<T>(this IQueryable<T> source, string propertyName)
    {
        return typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
            BindingFlags.Public | BindingFlags.Instance) != null;
    }

    public static IQueryable<T> OrderByProperty<T>(
       this IQueryable<T> source, string propertyName)
    {
        if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase | 
            BindingFlags.Public | BindingFlags.Instance) == null)
        {
            return null;
        }
        ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
        Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
        LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
        MethodInfo genericMethod = 
          OrderByMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
        object ret = genericMethod.Invoke(null, new object[] {source, lambda});
        return (IQueryable<T>) ret;
    }

    public static IQueryable<T> OrderByPropertyDescending<T>(
        this IQueryable<T> source, string propertyName)
    {
        if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase | 
            BindingFlags.Public | BindingFlags.Instance) == null)
        {
            return null;
        }
        ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
        Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
        LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
        MethodInfo genericMethod = 
          OrderByDescendingMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
        object ret = genericMethod.Invoke(null, new object[] {source, lambda});
        return (IQueryable<T>) ret;
    }
}

用法

string orderBy = "Name";
if (query.PropertyExists(orderBy))
{
   query = query.OrderByProperty(orderBy);
   - OR - 
   query = query.OrderByPropertyDescending(orderBy);
}

【讨论】:

  • 我们真的希望其他人有答案吗? ;)
  • 是否可以修改它以同时对当前实体的导航属性进行排序?
  • 优秀的解决方案!我唯一的问题是如何按从属对象订购?例如,如果我有一个对象Apple { Kernel { Size = 13 } },我将如何对Size 属性进行查询?将示例中的orderBy 设置为"Kernel.Size" 不起作用。
  • 这不适用于 EF Core 3.1。有关如何使其正常工作的任何建议?
【解决方案2】:

恕我直言,我有一个更简单的解决方案:

public static IOrderedQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source, bool ascending , string sortingProperty)
{
    if (ascending)
        return source.OrderBy(item => item.GetReflectedPropertyValue(sortingProperty));
    else
        return source.OrderByDescending(item => item.GetReflectedPropertyValue(sortingProperty));
}

private static object GetReflectedPropertyValue(this object subject, string field)
{
    return subject.GetType().GetProperty(field).GetValue(subject, null);
}

用法是:

myQueryableCollection.Sort(ascending: true, "Name")

当然,PropertyExist() 助手是一个很好的补充......

【讨论】:

  • 嗨,这在 EF Core 3 中有效吗?它为我抛出: .OrderByDescending(e0 => (object)e0 .GetReflectedPropertyValue(__filter_Sort_Predicate_3))' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToLi st() 或 ToListAsync() 的调用显式切换到客户端评估。请参阅go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。
  • 不起作用:The LINQ expression 'DbSet&lt;ImportLog&gt; .Where(i =&gt; .....) .OrderBy(i =&gt; (object)i .GetReflectedPropertyValue(__sortingProperty_0))' could not be translated.
猜你喜欢
  • 1970-01-01
  • 2010-12-14
  • 2013-06-06
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多