简体   繁体   中英

Dynamic LINQ Sub Query

I have LINQ query that I want to generate dynamically:

var groupData =
    from l in data
    group l by l.Field1 into field1Group
    select new MenuItem()
    {
        Key = field1Group.Key,
        Count = field1Group.Count(),
        Items = (from k in field1Group
                 group k by k.Field2 into field2Group
                 select new MenuItem()
                 {
                     Key = field2Group.Key,
                     Count = field2Group.Count()
                 }).ToList()
    };

The ultimate goal is to be able to dynamically group the data by any combination of fields with no limit on the nested queries.

I can get as far as the first level but I'm struggling with the nested sub queries:

string field1 = "Field1";
string field2 = "Field2";

var groupDataD =
    data.
    GroupBy(field1, "it").
    Select("new ( it.Key, it.Count() as Count )");

Is this possible with chained dynamic LINQ? Or is there a better way to achieve this?

The following should work (though personally I would rather avoid using such code):

  1. Follow this answer to add the following in ParseAggregate , :

     Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) { // Change starts here var originalIt = it; var originalOuterIt = outerIt; // Change ends here outerIt = it; ParameterExpression innerIt = Expression.Parameter(elementType, elementType.Name); it = innerIt; Expression[] args = ParseArgumentList(); // Change starts here it = originalIt; outerIt = originalOuterIt; // Change ends here ... }
  2. Add Select , GroupBy , ToList into IEnumerableSignatures , and respective conditions in ParseAggregate , as explained in this answer :

     interface IEnumerableSignatures { ... void GroupBy(object selector); void Select(object selector); void ToList(); ... } Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) { ... if (signature.Name == "Min" || signature.Name == "Max" || signature.Name == "GroupBy" || signature.Name == "Select") ... }
  3. Finally, Your query would be:

     string field1 = "Field1"; string field2 = "Field2"; var result = data .GroupBy(field1, "it") .Select($@"new ( it.Key, it.Count() as Count, it.GroupBy({field2}) .Select(new (it.Key, it.Count() as Count)) .ToList() as Items )");

    Note that "it" holds a different instance when used in the parent query vs. the subquery. I tried to take advantage of "outerIt" to overcome this conflation, but unfortunately without success (but maybe you'd succeed? maybe 1 , 2 would help)

  4. A simple example for future reference:

     public class Person { public string State { get; set; } public int Age { get; set; } } public static Main() { var persons = new List<Person> { new Person { State = "CA", Age = 20 }, new Person { State = "CA", Age = 20 }, new Person { State = "CA", Age = 30 }, new Person { State = "WA", Age = 60 }, new Person { State = "WA", Age = 70 }, }; var result = persons .GroupBy("State", "it") .Select(@"new ( it.Key, it.Count() as Count, it.GroupBy(Age) .Select(new (it.Key, it.Count() as Count)) .ToList() as Items )"); foreach (dynamic group in result) { Console.WriteLine($"Group.Key: {group.Key}"); foreach (dynamic subGroup in group.Items) { Console.WriteLine($"SubGroup.Key: {subGroup.Key}"); Console.WriteLine($"SubGroup.Count: {subGroup.Count}"); } } }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM