Search code examples
c#mongodb.net-coreaggregation-frameworkmongodb-.net-driver

Field must be an accumulator object error when trying to group by object as key


I'm trying to type-safe group documents by multiple properties from a collection. I figured out that the problem is not the grouping by multiple properties itself but that the key is an object (new { }) instead of a string.

This works:

collection.Aggregate().Group(x => x.Name, x => new { Name = x.Key, Count = x.Sum(s => 1) }).ToList();

This does not work:

collection.Aggregate().Group(x => new { Name = x.Name }, x => new { Name = x.Key.Name, Count = x.Sum(s => 1) }).ToList();

The Error is:

MongoDB.Driver.MongoCommandException: 'Command aggregate failed: The field 'Name' must be an accumulator object.'

When I convert the query that doesn't work to a string I get the following:

aggregate([{ "$group" : { "_id" : { "Name" : "$Name" }, "Name" : "$_id.Name", "Count" : { "$sum" : 1 } } }])

I guess the problem here is this part "Name" : "$_id.Name". How do I fix this problem?

I'm currently using version 2.8.0 of the mongodb driver.


Solution

  • Actually the driver behaves correctly here but what you're experiencing is the limitation of $group pipeline stage in Aggregation Framework. The documentation states that:

    The output documents contain an _id field which contains the distinct group by key. The output documents can also contain computed fields that hold the values of some accumulator expression grouped by the $group’s _id field

    So in your case you're trying to refer to _id field without any accumulator.

    From the logical perspective your queries appear to be similar. First one gets translated into:

    {
        "$group" : {
            "_id" : "$Name",
            "Count" : {
                "$sum" : 1
            }
        }
    }
    

    Second one would return the same data but you have an object as a grouping key. To fix that in C# you need to introduce .First() expression which will be translated into $first, so running:

    Col.Aggregate()
        .Group(
            x => new { Name = x.Name }, 
            x => new { Name = x.First().Name, Count = x.Sum(s => 1) })
        .ToList();
    

    will run below query on the database:

    {
        "$group" : {
            "_id" : {
                "Name" : "$Name"
            },
            "Name" : {
                "$first" : "$Name"
            },
            "Count" : {
                "$sum" : 1
            }
        }
    }
    

    It works but logically it returns the same data as first aggregation