Reputation: 306
I'm fairly new to LINQ, and for the past few days, I've been struggling to achieve the following.
I need to group (as well, as query, and later do some aggregation stuff) a collection of rather complex objects.
The problem is that I need to do the query/grouping by single or multiple parameters, whose names are not known at design time. Ideally, I'd like to supply an array with strings representing the parameter names and get the the grouping done.
Additionally, the parameters in question are nested within a collection contained by objects-to-be-grouped.
Here is an example:
Objects in my collection:
Class qItem
Property qName As String
Property qStuff As String
Property qList As List(Of qTag)
End Class
Class qTag
Property Tag As String
Property Value As String
End Class
The main collection
Dim qResult As New List(Of qItem)
The collection is populated with
qResult.Add(New qItem With {.qName = "N1", .qStuff = "S1", .qList = New List(Of qTag) From {
New qTag With {.Tag = "T1", .Value = "VAA"},
New qTag With {.Tag = "NAME", .Value = "V2"}}})
qResult.Add(New qItem With {.qName = "N1", .qStuff = "S3", .qList = New List(Of qTag) From {
New qTag With {.Tag = "T1", .Value = "V1"},
New qTag With {.Tag = "NAME", .Value = "V2"}}})
qResult.Add(New qItem With {.qName = "N2", .qStuff = "S1", .qList = New List(Of qTag) From {
New qTag With {.Tag = "T1", .Value = "V1"},
New qTag With {.Tag = "NAME", .Value = "VZZ"}}})
qResult.Add(New qItem With {.qName = "N2", .qStuff = "S3", .qList = New List(Of qTag) From {
New qTag With {.Tag = "T1", .Value = "V1"},
New qTag With {.Tag = "NAME", .Value = "VZZ"}}})
qResult.Add(New qItem With {.qName = "N2", .qStuff = "S1", .qList = New List(Of qTag) From {
New qTag With {.Tag = "T1", .Value = "VAA"},
New qTag With {.Tag = "NAME", .Value = "VZZ"}}})
Now. I'd like to group objects in qResult
by the Value
property from qTag
object in the collection contained by the qItem
. The grouping should be done by Tag
properties of the qTag
object.
If I need to group by only one parameter, I can do this:
Dim R = From q In qResult
Group By q.qList.FirstOrDefault(Function(x) x.Tag = "NAME")?.Value
Into Group
Where "NAME"
is the value I can pass to do the grouping.
If I need to group by multiple parameters (for example "NAME"
and "T1"
), I learned, that I can do this:
Dim Rt = From q In qResult
Group By MyGrouping = New With {
Key .g1 = q.qList.FirstOrDefault(Function(x) x.Tag = "NAME")?.Value,
Key .g2 = q.qList.FirstOrDefault(Function(x) x.Tag = "T1")?.Value
}
Into Group
But now, I can no longer supply the grouping information dynamically. For that, I would somehow need to generate the anonymous type with dynamic properties.
After that I've looked into Dynamic LINQ as described here.
With that, I learned that I can do the grouping like this. For example, if I needed to group by simple properties of the qitem
Dim Rt1 = qResult.GroupBy("new (qName, qStuff)", "it")
This works and I can pass the information as array as well
Dim GroupingParams As String() = {"qName", "qStuff"}
Dim Rt2 = qResult.GroupBy("new (" & String.Join(",", GroupingParams) & ")", "it")
But this only works for properties of the qitem
. My initial goal was to group items by the Value
property in qTag
in a collection contained in qItem
.
It seems, that Dynamic LINQ does some sort of evaluating here "new (qName, qStuff)"
. Though, I could not find a comprehensive description of what can or cannot be evaluated. So this doesn't work
Dim Rt3 = qResult.GroupBy("qList.FirstOrDefault(Function(x) x.Tag = ""NAME"")?.Value,", "it")
Is it possible to pass this to Dynamic Linq?
Or should start looking into reflection or dynamic anonymous types? (though, I'm not sure whether the regular LINQ Group By
will accept such a anonymous type)
Upvotes: 1
Views: 447
Reputation: 26926
Yes, you don't use the lambda Function(x)
construct, it is implicit:
Dim Rt3 = qResult.AsQueryable.GroupBy("iif(qList.FirstOrDefault(Tag = ""NAME"") == null, null, qList.FirstOrDefault(Tag = ""NAME"").Value)", "it")
Note: You can't use a null-propagating operator in an Expression
tree (because C# has woefully neglected updating Expression
) and np()
isn't flexible enough to handle this, so you must manually expand out the test, and run FirstOrDefault
twice :( If you could get away with using First
instead, it is simply:
Dim Rt3 = qResult.AsQueryable.GroupBy("qList.First(Tag = ""NAME"").Value", "it")
For grouping by an anonymous object, you must make sure you don't have duplicate field names so use as
to name each field:
Dim Rt3 = qResult.AsQueryable.GroupBy("new (qList.FirstOrDefault(Tag = ""T1"").Value as T1,qList.FirstOrDefault(Tag = ""NAME"").Value as Name)", "it")
Upvotes: 1