【问题标题】:C# order List of Parent objects by Child's property via ReflectionC#通过反射按子属性排序父对象列表
【发布时间】:2019-10-08 18:36:12
【问题描述】:

我有一个方法可以通过反射对属性进行查询,如下所示:

   public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
   {
       string command = desc ? "OrderByDescending" : "OrderBy";
       var parts = orderByProperty.Split('.');
       var parameter = Expression.Parameter(typeof(TEntity), "p");
       Expression body = parameter;
       if (parts.Length > 1)
       {
           for (var i = 0; i < parts.Length - 1; i++)
           {
               body = Expression.Property(body, parts[i]);
           }
       }
       var property = body.Type.GetProperty(parts.LastOrDefault())
       body = Expression.Property(body, property.Name);
       var orderByExpression = Expression.Lambda(body, parameter);
       resultExpression = Expression.Call(typeof(Queryable), command, 
                                       new Type[] { typeof(TEntity), property.PropertyType },
                                       source.Expression, Expression.Quote(orderByExpression));
       return source.Provider.CreateQuery<TEntity>(resultExpression);
   }

如果我将它用于对象的属性或其子属性,它会起作用,但当子属性是 List 时,它就不起作用。

    // Success
    db.Users.OrderBy("Name", true)
    db.Users.OrderBy("CurrentAddress.City.Name", false)

    // Fail
    db.Users.OrderBy("PhoneLines.RenewalDate", true)

我的模型是这样的:

    public class User {
        public string Name { get; set; }
        public Address CurrentAddress{ get; set; }
        public List<Phone> PhoneLines { get; set; }
    }

我正在尝试做这样的工作:

    db.Users.OrderByDescending(x => 
        x.PhoneLines
          .OrderByDescending(y => y.RenewalDate)
          .Select(y => y.RenewalDate).FirstOrDefault()
    );

现在我只对使这项工作适用于第一级 List 子级感兴趣,也就是说,我不关心级联列表场景,只有链中的一个子级是一个列表。

到目前为止,我的尝试都失败了。我正在检查最后一个属性是否是一个集合,如果是,我尝试为列表创建一个 lambda,并为类的元素目标 order 属性创建另一个 lambda。


if (body.Type.Namespace.Equals("System.Collections.Generic"))
{
 var mainType = body.Type.GenericTypeArguments.FirstOrDefault();
 var parameterInner = Expression.Parameter(mainType, "x");
 var mainProperty = Expression.Property(parameterInner, property.Name);
 var orderByExpressionInner = Expression.Lambda(mainProperty, parameterInner);
 var orderByExpression = Expression.Lambda(body, parameter);

 var selM = typeof(Enumerable).GetMethods()
             .Where(x => x.Name == command)
             .First().MakeGenericMethod(mainType, property.PropertyType);

 var resultExpressionOuter = Expression.Call(typeof(Queryable), 
                            command, new Type[] { typeof(TEntity), body.Type },
                            source.Expression, 
                            Expression.Call(null, selM, parameterInner, orderByExpressionInner
                        ));
}

我对反射的了解有限,是否可以通过反射来实现?

【问题讨论】:

  • So far my attempts have failed 你能详细说明一下吗?会发生什么,你会期待什么?
  • 最新的尝试导致以下异常:Expression of type 'Phone' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable'1[Phone]' of method 'System.Linq.IOrderedEnumerable'1[Phone] OrderBy[Phone,System.DateTime](System.Collections.Generic.IEnumerable'1[Phone], System.Func'2[Phone,System.DateTime])'我希望它能够构建一个表达式成功地用source.Provider.CreateQuery&lt;TEntity&gt;(resultExpression)订购一个EF数据集

标签: c# reflection iqueryable


【解决方案1】:

一种选择是在您的父类上放置一个新属性,并使其负责返回您需要的值。比如这样;

public class User
{
    public string Name { get; set; }
    public Address CurrentAddress { get; set; }
    public List<Phone> PhoneLines { get; set; }

    private DateTime _dummyValue;
    public DateTime GetRenewalDate
    {
        get
        {
            if (this.PhoneLines == null || this.PhoneLines.Count == 0)
                return DateTime.MinValue;
            else
            {
                return this.PhoneLines.OrderByDescending(y => y.RenewalDate).Select(y => y.RenewalDate).FirstOrDefault();
            }
        }
        set
        {
             _dummyValue = value;
        }
    }
 }

如果你的列表为空或为空,请确保你有默认处理,这里我返回 DateTime 的 MinValue。

编辑: 为了解决您在 cmets 中提出的另一个问题

LINQ 不支持指定的类型成员“GetRenewalDate” 实体。只有初始化器、实体成员和实体导航 支持属性。

您可以尝试添加一个虚拟集,这也可以作为一种解决方法(但并不优雅)。 我已经编辑了上面的代码来显示这个

【讨论】:

  • 这是一个聪明的解决方案,但它正在抛出'The specified type member 'GetRenewalDate' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
  • 哦,好的! “肮脏”的解决方案是转换为列表。在此处查看第二个答案:stackoverflow.com/questions/11584660/…
  • @BrunoDiGiuseppe 我已经更新了使用虚拟集的解决方案,这有时可行但很脏!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-06
  • 2019-03-09
相关资源
最近更新 更多