Динамический порядок linq по вложенному свойству с нулевыми свойствами

Я использую эту динамическую функцию упорядочивания linq, которую я получил от здесь.

Это прекрасно работает с вложенными свойствами, поэтому я могу это сделать:

var result = data.OrderBy("SomeProperty.NestedProperty");

Проблема заключается в том, что если SomeProperty имеет значение null, то выполнение OrderBy в NestedProperty бросает печально известную "ссылку на объект, не установленную на экземпляр объекта".

Я предполагаю, что мне нужно настроить следующие строки для обработки исключения:

expr = Expression.Property(expr, pi);

// Or

LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);    

Я думал о создании тела оператора, где я мог бы в худшем случае использовать try catch, но это не сработало, поскольку вы не можете иметь тела операторов в orderby выражения linq: "Ярб-выражение с телом оператора не может преобразуется в дерево выражений"

Я потерялся здесь, любые предложения о том, как я могу это сделать?

Кстати, это для Linq для объектов, а не для базы данных.

Ответ 1

static void Main(string[] args)
{
    var data = new List<MyType>() {
        new MyType() { SomeProperty = new Inner() { NestedProperty = "2" }},
        new MyType() { SomeProperty = new Inner() { NestedProperty = "1" }},
        new MyType() { SomeProperty = new Inner() { NestedProperty = "3" }},
        new MyType(),
    }.AsQueryable();
    var sorted = data.OrderBy(x => GetPropertyValue(x, "SomeProperty.NestedProperty"));

    foreach (var myType in sorted)
    {
       try
       {
          Console.WriteLine(myType.SomeProperty.NestedProperty);
       }
       catch (Exception e)
       {
          Console.WriteLine("Null");
       }
    }
}

public static object GetPropertyValue(object obj, string propertyName)
{
    try
    {
        foreach (var prop in propertyName.Split('.').Select(s => obj.GetType().GetProperty(s)))
        {
            obj = prop.GetValue(obj, null);
        }
        return obj;
    }
    catch (NullReferenceException)
    {
        return null;
    }
}

Ответ 2

Как насчет дженериков:

Помощник:

public static Expression<Func<TEntity, TResult>> GetExpression<TEntity, TResult>(string prop)
        {
            var param = Expression.Parameter(typeof(TEntity), "p");
            var parts = prop.Split('.');

            Expression parent = parts.Aggregate<string, Expression>(param, Expression.Property);
            Expression conversion = Expression.Convert(parent, typeof (object));

            var tryExpression = Expression.TryCatch(Expression.Block(typeof(object), conversion),
                                                    Expression.Catch(typeof(object), Expression.Constant(null))); 

            return Expression.Lambda<Func<TEntity, TResult>>(tryExpression, param);
        }

Иерархия образцов:

public class A
    {
        public A(B b)
        {
            B = b;
        }

        public B B { get; set; }
    }

    public class B
    {
        public B(C c)
        {
            C = c;
        }

        public C C { get; set; }
    }

    public class C
    {
        public C(int id)
        {
            this.Id = id;
        }

        public int Id { get; set; }
    }

Пример:

var list = new List<B>
            { 
                new B(new A(new C(1))),
                new B(new A(new C(2))),
                new B(new A(new C(3))),
                new B(new A(null)),
                new B(null)
            }.AsQueryable();

var ordered = list.OrderByDescending(GetExpression<B, Object>("AProp.CProp.Id"));

Вывод:

3
2
1
Null
Null