如何使用LINQ或Lambda根据一个子属性来排序一个集合?[英] How do I order a collection based on a child property using LINQ or a Lambda?

本文是小编为大家收集整理的关于如何使用LINQ或Lambda根据一个子属性来排序一个集合?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我得到以下 string 表达式:

"ChildObject.FullName" ...其中 ChildObject 是 MyObject1 类型的实例属性.

ChildObject 有一个名为"FullName"的属性,我想根据这个子属性"FullName"的值对"MyObject1"类型的集合进行排序.

我可以整天直接在 MyObject1 上的属性上执行此操作,但在子实例上执行此操作时遇到了 2 个挑战,并且我无法让所有部分都正常工作.主要的两个挑战是:

  1. MyObject1 有几个不同的子属性类型,所以我不能硬编码 ChildObject 的类型.字符串可以是任何类型.
  2. 排序表达式是字符串,不是已知类型.

对于上面的#2,我可以使用反射来获取子属性的类型,但我无法让它全部工作.我有以下内容,它可以编译并运行,但没有任何不同的排序:

'SortExpression below is a String like: "ChildObject.FullName"
MyObject1List = MyObject1List.OrderBy(Function(x)
      Dim t As Type = x.GetType()
      Dim tp As Type = t.GetProperty(SortExpression.Split(".").ElementAt(0)).PropertyType()
      Return tp.GetProperty(Request.CompareExpression.Split(".").ElementAt(1))
      End Function).ToList()

在表达式最后一行返回的值之上(如果我在 OrderBy 方法之外运行代码,确实为我提供了我需要的"全名"信息.所以代码必须接近,但是还是不行.

关于如何实现这一点的任何想法?我试图阻止的是硬编码一系列'If'块在孩子的类型上,然后将其类型硬编码到 sort 或 OrderBy 方法.

谢谢!

推荐答案

如果我理解正确,你有一个对象(比如,Parent),其中包含一个对象(比如,Child).Child 有一个 FullName 字段,您想按子 FullName 对父母列表进行排序.

如果是这样,那么 OrderBy() 应该为您完成.

假设我们有一个父母名单

父母{ Id = 1,孩子 = { Id = 1,FullName = "Jan"}}父母{ Id = 2,孩子 = { Id = 2,FullName = "Feb"}}父母{ ID = 3,孩子 = { ID = 3,全名 ="Mar"}}父{ ID = 4,子 = { ID = 4,全名 = "Apr"}}

使用 OrderBy() 对它们进行排序

Dim sorted = Items.OrderBy(Function(itm) itm.Child.FullName)

给予

父母{ Id = 4,孩子 = { Id = 4,FullName = "Apr"}}父母{ Id = 2,孩子 = { Id = 2,FullName = "Feb"}}父{ ID = 1,子 = { ID = 1,全名 ="Jan"}}Parent{ Id = 3, Child = { Id = 3, FullName = "Mar"}}

(以下示例)

第,
艾伦.

编辑

只需重新阅读问题.您有不同的按名称选择的 FullName 属性(来自查询字符串?)

您可以使用表达式按名称选择属性(请参阅 如何在运行时在 IEnumerable<T> 上创建动态选择?) 用于一般形状.

如果选定的属性是 IComparable(或者是 IEquatable?不太确定是哪个),那么 OrderBy() 仍然可以工作.这意味着只要排序字段是基本类型就可以了.如果它们是自定义类型(对象),您将需要做更多的工作......

对第一个错误答案感到抱歉.

更多编辑

现在是星期五,这里很慢:?

好的,扩展答案以按名称访问不同的子成员.(我使用字段而不是属性,但两者都可以).我们仍然需要知道字段的类型,但可能需要做更多的工作才能删除它(如果需要).

Private Class Child
Public Id As Integer
Public FullName As String
End Class

Private Class Parent
Public Id As Integer
Public Child As Child
End Class

Private Items As New List(Of Parent)() From { _
New Parent() With { _
    Key .Id = 1, _
    Key .Child = New Child() With { _
        Key .Id = 1, _
        Key .FullName = "Jan" _
    } _
}, _
New Parent() With { _
    Key .Id = 2, _
    Key .Child = New Child() With { _
        Key .Id = 2, _
        Key .FullName = "Feb" _
    } _
}, _
New Parent() With { _
    Key .Id = 3, _
    Key .Child = New Child() With { _
        Key .Id = 3, _
        Key .FullName = "Mar" _
    } _
}, _
New Parent() With { _
    Key .Id = 4, _
    Key .Child = New Child() With { _
        Key .Id = 4, _
        Key .FullName = "Apr" _
    } _
} _
}

<TestMethod> _
Public Sub SortByChildName()
Dim expectedParentIds = New () {4, 2, 1, 3}
Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, String)("Child.FullName")).[Select](Function(itm) itm.Id)

Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))
End Sub

<TestMethod> _
Public Sub SortByChildId()

Dim expectedParentIds = New () {4, 3, 2, 1}
Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, Integer)("Child.Id")).[Select](Function(itm) itm.Id)

Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))

End Sub


Public Shared Function SelectExpression(Of TItem, TField)(fieldNames As String) As Func(Of TItem, TField)

Dim type = GetType(TItem)
Dim fields = fieldNames.Split("."C)

Dim arg As ParameterExpression = Expression.Parameter(type, "item")
Dim expr As Expression = arg

For Each field As String In fields
    Dim fieldInfo = type.GetField(field)
    expr = Expression.Field(expr, fieldInfo)
    type = fieldInfo.FieldType
Next

Return Expression.Lambda(Of Func(Of TItem, TField))(expr, arg).Compile()

End Function

本文地址:https://www.itbaoku.cn/post/1556906.html

问题描述

I get the following string expression provided:

"ChildObject.FullName" ...where ChildObject is an instance property on the MyObject1 type.

ChildObject has a property named "FullName" and I want to sort a collection of type "MyObject1" based on this child properties "FullName" value.

I can do this all day long on properties directly on MyObject1 but I run into 2 challanges when doing it on a child instance and I can't get all the pieces working. The main 2 challanges are:

  1. MyObject1 has a few different child property types so I can't hardcode the type for ChildObject. The string could be of any type.
  2. The sort expression is a String and not a known type.

For #2 above I can use Reflection to get the type of the child property, but I just can't get it all to work. I have the following below and it compiles and runs, but does not sort any differently:

'SortExpression below is a String like: "ChildObject.FullName"
MyObject1List = MyObject1List.OrderBy(Function(x)
      Dim t As Type = x.GetType()
      Dim tp As Type = t.GetProperty(SortExpression.Split(".").ElementAt(0)).PropertyType()
      Return tp.GetProperty(Request.CompareExpression.Split(".").ElementAt(1))
      End Function).ToList()

Above the value returned from the last line in the expression (if I run the code outsode the OrderBy method, does provide me the 'FullName' information I need. So the code must be close, but it still does not work.

Any ideas on how I can accomplish this? What I am trying to prevent is hardcoding a series of 'If' blocks on the child's type to then hardcode in its type to the sort or OrderBy method.

Thanks!

推荐答案

If I understand this correctly you have an object (say, Parent) which contains an object (say, Child). Child has a field FullName and you want to sort a list of parents by the child FullName.

If so, then then OrderBy() should do it for you.

Say we have a list of parents

Parent{ Id = 1, Child = { Id = 1, FullName = "Jan"}} Parent{ Id = 2, Child = { Id = 2, FullName = "Feb"}} Parent{ Id = 3, Child = { Id = 3, FullName = "Mar"}} Parent{ Id = 4, Child = { Id = 4, FullName = "Apr"}}

sorting them using OrderBy()

Dim sorted = Items.OrderBy(Function(itm) itm.Child.FullName)

gives

Parent{ Id = 4, Child = { Id = 4, FullName = "Apr"}} Parent{ Id = 2, Child = { Id = 2, FullName = "Feb"}} Parent{ Id = 1, Child = { Id = 1, FullName = "Jan"}} Parent{ Id = 3, Child = { Id = 3, FullName = "Mar"}}

(example below)

hth,
Alan.

Edit

Just re-read the question. You have different FullName properties which are selected by name (from a query string?)

You can select the property by name using an expression (see, How can I create a dynamic Select on an IEnumerable<T> at runtime?) for the general shape.

If the selected property is IComparable (Or is it IEquatable? Not too sure which) then the OrderBy() will still work. This means that as long as the sort fields are basic types you are fine. If they are custom types (objects) you will need to do some more work...

Sorry about the first mis-answer.

More Edits

It's Friday and slow in here :?

OK, expanded the answer to access different child memebers by name. (I used Fields rather than Properties but either will work). We still need to know the type of the field but a bit more work might remove that (if needed).

Private Class Child
Public Id As Integer
Public FullName As String
End Class

Private Class Parent
Public Id As Integer
Public Child As Child
End Class

Private Items As New List(Of Parent)() From { _
New Parent() With { _
    Key .Id = 1, _
    Key .Child = New Child() With { _
        Key .Id = 1, _
        Key .FullName = "Jan" _
    } _
}, _
New Parent() With { _
    Key .Id = 2, _
    Key .Child = New Child() With { _
        Key .Id = 2, _
        Key .FullName = "Feb" _
    } _
}, _
New Parent() With { _
    Key .Id = 3, _
    Key .Child = New Child() With { _
        Key .Id = 3, _
        Key .FullName = "Mar" _
    } _
}, _
New Parent() With { _
    Key .Id = 4, _
    Key .Child = New Child() With { _
        Key .Id = 4, _
        Key .FullName = "Apr" _
    } _
} _
}

<TestMethod> _
Public Sub SortByChildName()
Dim expectedParentIds = New () {4, 2, 1, 3}
Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, String)("Child.FullName")).[Select](Function(itm) itm.Id)

Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))
End Sub

<TestMethod> _
Public Sub SortByChildId()

Dim expectedParentIds = New () {4, 3, 2, 1}
Dim sortedIds = Items.OrderBy(SelectExpression(Of Parent, Integer)("Child.Id")).[Select](Function(itm) itm.Id)

Assert.IsTrue(expectedParentIds.SequenceEqual(sortedIds))

End Sub


Public Shared Function SelectExpression(Of TItem, TField)(fieldNames As String) As Func(Of TItem, TField)

Dim type = GetType(TItem)
Dim fields = fieldNames.Split("."C)

Dim arg As ParameterExpression = Expression.Parameter(type, "item")
Dim expr As Expression = arg

For Each field As String In fields
    Dim fieldInfo = type.GetField(field)
    expr = Expression.Field(expr, fieldInfo)
    type = fieldInfo.FieldType
Next

Return Expression.Lambda(Of Func(Of TItem, TField))(expr, arg).Compile()

End Function