Emi
Emi

Reputation: 306

LINQ GroupBy with multiple dynamic complex parameters

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

Answers (1)

NetMage
NetMage

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

Related Questions