C # order Список родительских объектов по свойству Child через Reflection - PullRequest
1 голос
/ 22 мая 2019

У меня есть метод, который упорядочивает запрос по свойству через отражение, например:

   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()
    );

В данный момент меня интересует создание этой работы только для дочерних элементов первого уровня, то есть меня не интересует сценарий каскадного списка, только один дочерний элемент в цепочке является списком.

Пока чтомои попытки потерпели неудачу.Я проверяю, является ли последнее свойство коллекцией, и если это так, я попытался создать лямбду для списка и еще одну лямбду для свойства целевого порядка элемента класса.


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

Мои знания об отражении ограничены,Можно ли достичь этого с помощью отражения?

1 Ответ

1 голос
/ 22 мая 2019

Один из вариантов - разместить новое свойство в родительском классе и возложить на него ответственность за возврат требуемого значения. Такие как это;

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

Убедитесь, что у вас есть обработка по умолчанию, если ваш список пуст или нулевой, здесь я возвращаю MinValue DateTime.

Edit: Чтобы обойти другую проблему, вы поставили в комментариях

Указанный член типа GetRenewalDate не поддерживается в LINQ для Сущности. Только инициализаторы, элементы сущностей и навигация сущностей свойства поддерживаются.

Вы можете попробовать добавить фиктивный набор, это также может работать как обходной путь (но это не элегантно). Я отредактировал код выше, чтобы показать это

...