使用动态LINQ按嵌套属性排序,其中包含空属性。

14

我正在使用这个动态linq orderby函数,我从这里得到它。

这对于嵌套属性很有效,所以我可以这样做:

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

问题在于如果SomeProperty为空,则对NestedProperty执行OrderBy操作时会抛出臭名昭著的“对象引用未设置为对象实例”的异常。

我猜想我需要自定义以下代码行以处理异常:

expr = Expression.Property(expr, pi);

// Or

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

我曾考虑创建一个语句体,在最坏的情况下使用try catch,但这并不起作用,因为在linq语句中orderby排序无法使用语句体:“带有语句体的lambda表达式无法转换为表达式树”。

我很困惑,请问有什么建议可以实现这个功能吗?

顺便说一句,这是针对于Linq to Objects,与数据库无关。


我猜这行代码 expr = Expression.Property(expr, pi); 会将 expr 设置为 null,而后续的代码没有处理它。最简单的解决方法是 expr = Expression.Property(expr, pi) ?? default(T);。但是在这种情况下,您需要检查是否满意所应用的顺序。 - Tommi
这是一个很好的观点,实际上这会使排序工作出现错误,理想情况下,空值应该被“分组”在一起。 - user2535425
我想他们会这样做:如果属性是字符串,则默认值将为String.Empty,并且所有具有空属性的项将根据升序/降序排序推送到集合的前面或后面。 - Tommi
@Tommi 我尝试了你的默认(T)来看看它的工作原理,但我无法编译它,出现了“运算符??不能应用”的错误。 - user2535425
它可能可以,但我找不到如何将其翻译成表达式语法,因为我的示例是IF这不是动态的。 - user2535425
显示剩余3条评论
2个回答

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

非常出色,这个看起来比Marc Gravel的答案简单,这让我想知道。不过,我的所有测试似乎都很好用,你让我感到非常开心。 - user2535425
非常聪明。递归的技巧很好。微软应该在未来的框架中提供这个作为扩展方法。 - arviman
如果 SomeProperty 是一个 List<SomeProperty>,那么上面的代码需要如何修改来处理这种情况? - Kappacake
1
@demonicdaron,我不知道你想如何访问该属性,但假设你想要类似于GetPropertyValue(x, "SomeProperty[0].NestedProperty")这样的东西,那么你需要修改GetPropertyValue以额外解析方括号中的索引。然后,你可以将此索引用于转换为IList的当前属性上,我猜测是这样的。 - Tommi

6

How about generics:

Helper Method:

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

  1. 如果TResult是值类型,解决方案将无法工作,因为它会尝试将null强制转换为该值类型,并导致另一个NullReferenceException。最好使用Expression.Default(typeof(TResult))代替Expression.Constant(null)。
  2. 如果使用上述建议,则无需转换为对象
  3. 我建议通过使用Expression.Catch(typeof(NullReferenceException)...来缩小捕获的异常范围。
- jonh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接