[CSHARP-3291] C# Aggregate "Group" after "Unwind" uses the wrong field name Created: 17/Dec/20 Updated: 27/Oct/23 Resolved: 16/Feb/22 |
|
| Status: | Closed |
| Project: | C# Driver |
| Component/s: | Linq |
| Affects Version/s: | 2.11.5 |
| Fix Version/s: | None |
| Type: | Bug | Priority: | Major - P3 |
| Reporter: | Daniel Arkley | Assignee: | James Kovacs |
| Resolution: | Works as Designed | Votes: | 1 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Environment: |
Windows 10, .NET 5 |
||
| Epic Link: | CSHARP-3615 |
| Description |
|
Suppose I have a collection, mapped in C# as a "DrivenRoute", held in MongoDB as:
Each entry in "Stops" is mapped in C# as a "DrivenRouteStop".
I want to count how many times each StationId has appeared in any DrivenRoute. In MQL I can use:
This produces the correct result:
Now I try to write the same query in C# using the aggregation pipeline syntax against strongly typed objects:
Whilst this pipeline runs, I receive an incorrect result:
It appears that the C# driver is generating the wrong path on the "unwind" stage. Instead of using "$Stops.StationId", it simply uses "StationId". The unwind operation correctly preserves the "Stops" field as an object, but C# tries to access StationId directly as if ReplaceRoot had been used.
Whilst I could revert to writing this query using BsonDocument instead of strongly typed objects, this defeats the purpose of being able to use strongly typed pipelines, and could result in incorrect results due to field name changes or similar, which wouldn't be picked up by the compiler at compile-time, or by Intellisense at design-time. |
| Comments |
| Comment by James Kovacs [ 17/Feb/22 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Whether to use Fluent Aggregate or LINQ is a matter of personal preference. Fluent Aggregate more closely maps to server operations. LINQ allows you to express queries in a more C# language idiomatic way and tends to be higher level. We will render multiple pipeline stages to express certain LINQ operations like GroupBy correctly whereas with Fluent Aggregate, you would have to write each stage yourself. Typically I lean more towards LINQ as it feels more natural to a C# developer. But if someone handed me a complex MQL aggregation written in the mongosh shell, it would likely be easier to duplicate in Fluent Aggregate than trying to figure out the equivalent LINQ. Fluent Aggregate also allows for raw BSON stage definitions, which you can't do with LINQ. This allows you to use features (like Atlas Search) where we haven't built a fluent API for the feature yet. In summary, both query techniques have their place and both should allow you to build the queries needed by your application. They can also be used interchangeably and together within the same application. There is no need to limit yourself to just Fluent Aggregate or LINQ or Fluent Find or any other technique. If one query is more easily expressible in LINQ and the next in Fluent Aggregate, mix and match as needed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Daniel Arkley [ 17/Feb/22 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Thanks James, this makes perfect sense. Is there any benefit to using the Fluent Aggreate approach vs the LINQ approach, or is what I'm doing using LINQ "valid"? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by James Kovacs [ 16/Feb/22 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
In triaging outstanding LINQ issues, I re-analyzed this issue in more depth and I believe that I have uncovered the root cause of the problem. When performing an $unwind operation on your DrivenRoute collections, you get the following results. (I removed the _t parameter to simplify the repro.)
Note that the resulting documents are no longer DrivenRoute objects because the Stops parameter went from a collection to a single value. You are now dealing with an UnwoundDrivenRoute for lack of a better name. I'll define these using C# records for brevity, but the same holds true for C# classes.
You told Unwind that the output type was DrivenRouteStop. This transformation from DrivenRoute to DrivenRouteStop happens on the server side. There is no easy way for us to check whether the defined output type is correct. The type parameters make the C# compiler happy even if those type parameters are incorrect. The following modified Fluent Aggregate query using UnwoundDrivenRoute produces the correct MQL and correct results.
The output is:
When using LINQ, we generate all the necessary aggregation stages and can internalize the complexity of $unwind outputting a different type. That's why your LINQ formulation worked, but your Fluent Aggregate query did not. Please feel free to re-open this issue if the suggested change does not produce the expected results in your use case. Sincerely, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by John Waters [ 19/Sep/21 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
This bug appears to still be around. Sadly, I can't use the workaround above due to another bug... I need to filter by only those array elements where a field in that element is in a list of ids I have in memory. As Linq Contains doesn't work for array (is one of) in MongoDB, I have to use the FilterBuilder version, AnyIn. But I can't combine FilterBuilder and Linq here... so either I can't filter my subelements or I can't unwind and group them. Is this bug going to be fixed? (psuedo code: I have a collection of Devices, and each Device has a property Seats[]. Each Seat has a CodeId. I have an array of CodeIds. I want a query to count the number of Seats grouped by CodeId. but only for those seats with CodeId matching any item in my array of CodeIds.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Mikalai Mazurenka (Inactive) [ 17/Dec/20 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hi daniel.arkley@gmail.com! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Daniel Arkley [ 17/Dec/20 ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
As an aside, the following C# query works correctly:
|