[CSHARP-3845] Deserialize Select of anonymous types using default values for missing fields Created: 12/Sep/21 Updated: 28/Oct/23 Resolved: 18/Dec/21 |
|
| Status: | Closed |
| Project: | C# Driver |
| Component/s: | None |
| Affects Version/s: | None |
| Fix Version/s: | 2.15.0 |
| Type: | Bug | Priority: | Unknown |
| Reporter: | James Turner | Assignee: | Robert Stam |
| Resolution: | Fixed | Votes: | 1 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Issue Links: |
|
||||||||||||
| Description |
|
Take this model:
This data in the collection:
And this LINQ query:
This throws the "No matching creator found" exception because I can't specify default values for properties on anonymous types. I can't specify attributes on it etc. What I'm thinking is that maybe anonymous types should have assumed default values for all properties or otherwise ignored from the creator map processing. Anonymous types are an important piece of doing succinct LINQ select statements. I shouldn't need to declare a class manually (so I can add `BsonDefaultValue` attribute to the properties) just because the DB has less data than the model. Unfortunately this doesn't seem to be behaviour I can override on my end. I maintain MongoFramework and I can't really intercept the type and add default values myself. I mean, unless I'm going to write my own extension to LINQ Select, get the anonymous type out and manually register it myself before the driver even sees it. But that approach is likely error prone. So in summary, can the driver apply default values for all properties of anonymous types (as we can't set them ourselves). |
| Comments |
| Comment by Githook User [ 18/Dec/21 ] | ||||||
|
Author: {'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}Message: | ||||||
| Comment by James Turner [ 13/Sep/21 ] | ||||||
|
I've found something else - there is a subtle difference how `BsonClassMap` is created depending which part of the code it is created from. `SerializerBuilder.BuildProjectedSerializer` creates a `BsonClassMap` that doesn't call auto map which doesn't run any of the conventions (including `ImmutableTypeClassMapConvention`) so the properties are never flagged to have default values. The best I can gather why this is the case is this comment inside the `BuildProjectSerializer` method:
While I didn't know about that tidbit of the compiler using the same type if its the same shape (though it makes sense to do so), I'm not actually sure it is a problem. If two anonymous types have the same properties by name and by type, why would they deserialize differently? Or more to the point, how could they? We're inside the logic for building a projected serializer for an anonymous type - we know what the property types are - there shouldn't be another way it can be serialized unless I'm missing something. If I'm right that it isn't an issue, could the `BsonClassMap` generating code here more simply call `BsonClassMap.LookupClassMap(type)` instead? Then with the serializer, rather than creating the `BsonClassMapSerializer<T>` directly in the `SerializerBuilder.BuildProjectedSerializer`, would it be better to send the request to `BsonSerializer.LookupSerializer(type)` instead? It helps decouple the BSON class map logic from the serializer builder which also opens the door for overriding the behaviour externally in the future. To completely avoid the behaviour of this hard reference to creating a `BsonClassMapSerializer` dynamically when building the projection serializer, I'd need to process the LINQ query myself. My only alternative now is letting the driver translate the queryable (getting me an `AggregateQueryableExecutionModel` - still creating that the class map and serializer for the anonymous type in the process) to subsequently throw out and create/select my own serializer for the anonymous type before I pass it to the `PipelineDefinition<TInput, TOutput>.Create(stages, serializer)`. | ||||||
| Comment by James Turner [ 12/Sep/21 ] | ||||||
|
One thing I noticed while trying to find creative ways to get around this is that what I'm saying in the issue actually is implemented in the code: https://github.com/mongodb/mongo-csharp-driver/blob/b961b81cb7dc1ffe7262c55a227afad0aab5a994/src/MongoDB.Bson/Serialization/Conventions/ImmutableTypeClassMapConvention.cs#L77-L99 That snippet is of the `ImmutableTypeClassMapConvention` and should be applying a default value for properties of anonymous types. I guess then the next question is: Why doesn't it seem to be happening? |