|
This is my Warehouse class:
public class Warehouse
|
{
|
public string Id { get; private set; }
|
public string Name { get; private set; }
|
public LocationAddress? Address { get; private set; }
|
public WarehouseShelfSetting? ShelfSettings { get; private set; }
|
public WarehouseTrolleySettings? TrolleySettings { get; private set; }
|
|
public Warehouse(string name)
|
{
|
this.Name = name;
|
this.Id = ObjectId.GenerateNewId().ToString();
|
}
|
|
public Warehouse(string name, LocationAddress? address)
|
{
|
this.Name = name;
|
this.Address = address;
|
this.Id = ObjectId.GenerateNewId().ToString();
|
}
|
|
public Warehouse(string id, string name, LocationAddress? address)
|
{
|
this.Name = name;
|
this.Address = address;
|
this.Id = id;
|
}
|
|
public class WarehouseTrolleySettings
|
{
|
public LabellingStrategy SlotLabelling { get; set; }
|
public int NumberOfSlots { get; set; }
|
}
|
|
public class WarehouseShelfSetting
|
{
|
public LabellingStrategy ShelfLabelling { get; set; }
|
public LabellingStrategy SlotLabelling { get; set; }
|
}
|
|
public enum LabellingStrategy
|
{
|
Alphabetic,
|
Numeric
|
}
|
}
|
This is the document in the database:
{
|
"_id" : ObjectId("5c534452d3224cc69bdcb6ac"),
|
"Name" : "Centrallagret",
|
"Address" : {
|
"StreetAddress" : "Storgatan 1",
|
"StreetAddress2" : null,
|
"PostalCode" : "123 45",
|
"City" : "Stockholm",
|
"CountryCode" : "se"
|
}
|
}
|
This code has been the same for very long time, but today I upgraded the MongoDB.Driver package, and now I get this exception:
System.FormatException: An error occurred while deserializing the Address property of class Zwiftly.Items.Warehouses.Warehouse: No matching creator found.
|
---> MongoDB.Bson.BsonSerializationException: No matching creator found.
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.ChooseBestCreator(Dictionary`2 values)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.CreateInstanceUsingCreator(Dictionary`2 values)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
|
at MongoDB.Bson.Serialization.Serializers.SerializerBase`1.MongoDB.Bson.Serialization.IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
|
at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize(IBsonSerializer serializer, BsonDeserializationContext context)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)
|
--- End of inner exception stack trace ---
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeMemberValue(BsonDeserializationContext context, BsonMemberMap memberMap)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.DeserializeClass(BsonDeserializationContext context)
|
at MongoDB.Bson.Serialization.BsonClassMapSerializer`1.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
|
at MongoDB.Bson.Serialization.IBsonSerializerExtensions.Deserialize[TValue](IBsonSerializer`1 serializer, BsonDeserializationContext context)
|
at MongoDB.Driver.Core.Operations.CursorBatchDeserializationHelper.DeserializeBatch[TDocument](RawBsonArray batch, IBsonSerializer`1 documentSerializer, MessageEncoderSettings messageEncoderSettings)
|
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.CreateCursorBatch(BsonDocument commandResult)
|
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.CreateCursor(IChannelSourceHandle channelSource, BsonDocument commandResult)
|
at MongoDB.Driver.Core.Operations.FindCommandOperation`1.ExecuteAsync(RetryableReadContext context, CancellationToken cancellationToken)
|
at MongoDB.Driver.Core.Operations.FindOperation`1.ExecuteAsync(RetryableReadContext context, CancellationToken cancellationToken)
|
at MongoDB.Driver.Core.Operations.FindOperation`1.ExecuteAsync(IReadBinding binding, CancellationToken cancellationToken)
|
at MongoDB.Driver.OperationExecutor.ExecuteReadOperationAsync[TResult](IReadBinding binding, IReadOperation`1 operation, CancellationToken cancellationToken)
|
at MongoDB.Driver.MongoCollectionImpl`1.ExecuteReadOperationAsync[TResult](IClientSessionHandle session, IReadOperation`1 operation, ReadPreference readPreference, CancellationToken cancellationToken)
|
at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
|
at MongoDB.Driver.IAsyncCursorSourceExtensions.FirstOrDefaultAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
|
I backed version by version and found that this regression was introduced in version 2.10.2.
|
|
For an F# record like this:
type Fund = {
|
Id: string
|
CompanyId: string
|
LastChangeDate: DateTime
|
}
|
where the *LastChangeDate* field was added recently, I use ""MapProperty"" to set a default value for non existing values, and avoid the error:
> No matching creator found.: BsonSerializationException
map.MapProperty(fun f -> f.LastChangeDate).SetDefaultValue(DateTime(2000, 01, 01)) |> ignore
|
|
|
Hey knoopjohn@gmail.com ,
This program will fail, but if I remove the deletion of the optional property on the nested class, then it works.
|
the reason is related to the fact that we apply immutable convention only for classes that have a constructor with the same argument names as the class properties. In your example, the constructor of MyStandaloneEntity has 3 arguments, but the number of properties is 4.
But I can't do the same using the fluent configuration:
|
you can use a different overriding:
map.MapMember(x => x.MyOptionalProperty).SetDefaultValue(() => null);
|
|
|
Sorry to bombard you with questions, but I feel I need to understand all the ins and outs around this.
If I decorate the property with the attribute you suggested, then it works as I'd like it:
[BsonDefaultValue(null)]
|
public string? MyOptionalProperty { get; private set; }
|
But I can't do the same using the fluent configuration:
var map = BsonClassMap.RegisterClassMap<SharedClass>();
|
map.AutoMap();
|
map.MapMember(x => x.MyOptionalProperty).SetDefaultValue(null);
|
Because then I get this exception:
System.ArgumentNullException : Value cannot be null. (Parameter 'defaultValueCreator')
|
|
|
Also... how come this only applies to nested objects? Have a look at this example:
Model:
public class MyStandaloneEntity
|
{
|
public MyStandaloneEntity(string name, MyNestedClass myObject, string? myOptionalProperty = null)
|
|
{ Id = ObjectId.GenerateNewId().ToString(); Name = name; MyOptionalProperty = myOptionalProperty; MyObject = myObject; }
|
|
public string Id
|
|
{ get; private set; }
|
public string Name \{ get; private set; }
|
|
public MyNestedClass MyObject
|
|
{ get; private set; }
|
public string? MyOptionalProperty \{ get; private set; }
|
|
}
|
|
public class MyNestedClass
|
{
|
public MyNestedClass(string name, string? myOptionalProperty = null)
|
|
{ Name = name; MyOptionalProperty = myOptionalProperty; }
|
|
public string Name
|
|
{ get; private set; }
|
public string? MyOptionalProperty \{ get; private set; }
|
|
}
|
|
Program:
var collection = _mongoClient.GetDatabase("TestDb").GetCollection<MyStandaloneEntity>("MyStandaloneEntities");
|
var entity = new MyStandaloneEntity("Bengt", new MyNestedClass("Hej", "test"));
|
|
await collection.InsertOneAsync(entity);
|
|
var deletePropDefinition = Builders<MyStandaloneEntity>.Update.Unset(x => x.MyOptionalProperty);
|
var deleteChildPropDefinition = Builders<MyStandaloneEntity>.Update.Unset(x => x.MyObject.MyOptionalProperty);
|
await collection.UpdateOneAsync(x => x.Id == entity.Id, deletePropDefinition);
|
await collection.UpdateOneAsync(x => x.Id == entity.Id, deleteChildPropDefinition); // <-- **** This makes it fail ****
|
|
var readInstance = await collection.FindAsync(x => x.Id == entity.Id);
|
This program will fail, but if I remove the deletion of the optional property on the nested class, then it works.
|
|
Do you know if BsonDefaultValue(null) can be achieved using a convention? I would like missing properties to default to null by default (unless then property on the type isn't nullable of course).
|
|
Oh, I thought that since that parameter is nullable in the constructor, that it would still be allowed. Then I see how it's the same issue as the one you refered to. Thanks.
|
|
Your reproduction involves a document that is *missing* one of the fields of the Address document (no StreetAddress2).
{
|
"_id" : ObjectId("5c534452d3224cc69bdcb6ac"),
|
"Name" : "Centrallagret",
|
"Address" : {
|
"StreetAddress" : "test",
|
"PostalCode" : "test",
|
"City" : "test",
|
"CountryCode" : "se"
|
}
|
}
|
which means it is the same issue as CSHARP-3108.
You can tell the driver to use a default value of null when StreetAddress2 is missing using the [BsonDefaultValue(null)] annotation.
|
|
I managed to create a blank project that reproduced the issue.
You can find it here: https://github.com/johnknoop/CSHARP-3175-reproduction
|
|
It should be reproducible by just removing any one of the fields of the Address document in the JSON string.
But then it's the same situation as CSHARP-3108.
|
|
Yes, I'm using nullable reference types from C#8.
Let me see if I can get a fully reproducable example and send it.
|
|
I am unable to reproduce this.
Here's my full test program:
using MongoDB.Bson;
|
using MongoDB.Bson.IO;
|
using MongoDB.Bson.Serialization;
|
using System;
|
|
#nullable enable
|
|
namespace TestCSharp3175
|
{
|
public class Warehouse
|
{
|
public string Id { get; private set; }
|
public string Name { get; private set; }
|
public LocationAddress? Address { get; private set; }
|
public WarehouseShelfSetting? ShelfSettings { get; private set; }
|
public WarehouseTrolleySettings? TrolleySettings { get; private set; }
|
|
public Warehouse(string name)
|
{
|
this.Name = name;
|
this.Id = ObjectId.GenerateNewId().ToString();
|
}
|
|
public Warehouse(string name, LocationAddress? address)
|
{
|
this.Name = name;
|
this.Address = address;
|
this.Id = ObjectId.GenerateNewId().ToString();
|
}
|
|
public Warehouse(string id, string name, LocationAddress? address)
|
{
|
this.Name = name;
|
this.Address = address;
|
this.Id = id;
|
}
|
|
public class WarehouseTrolleySettings
|
{
|
public LabellingStrategy SlotLabelling { get; set; }
|
public int NumberOfSlots { get; set; }
|
}
|
|
public class WarehouseShelfSetting
|
{
|
public LabellingStrategy ShelfLabelling { get; set; }
|
public LabellingStrategy SlotLabelling { get; set; }
|
}
|
|
public enum LabellingStrategy
|
{
|
Alphabetic,
|
Numeric
|
}
|
}
|
|
public class LocationAddress
|
{
|
public LocationAddress(LocationAddress address)
|
: this(address.StreetAddress, address.StreetAddress2, address.PostalCode, address.City, address.CountryCode) { }
|
|
// [JsonConstructor]
|
public LocationAddress(string streetAddress, string? streetAddress2, string postalCode, string city, string countryCode)
|
{
|
this.StreetAddress = streetAddress;
|
this.PostalCode = postalCode;
|
this.City = city;
|
StreetAddress2 = streetAddress2;
|
CountryCode = countryCode;
|
}
|
|
public string StreetAddress { get; private set; }
|
public string? StreetAddress2 { get; private set; }
|
public string PostalCode { get; private set; }
|
public string City { get; private set; }
|
|
/// <summary>
|
/// Two-letter
|
/// </summary>
|
public string CountryCode { get; private set; }
|
}
|
|
public static class Program
|
{
|
public static void Main(string[] args)
|
{
|
var json =
|
"{" +
|
" \"_id\" : \"5c534452d3224cc69bdcb6ac\"" +
|
" \"Name\" : \"Centrallagret\"" +
|
" \"Address\" : {" +
|
" \"StreetAddress\" : \"Storgatan 1\"" +
|
" \"StreetAddress2\" : null" +
|
" \"PostalCode\" : \"123 45\"" +
|
" \"City\" : \"Stockholm\"" +
|
" \"CountryCode\" : \"se\"" +
|
" }" +
|
"}";
|
|
var document = BsonSerializer.Deserialize<Warehouse>(json);
|
Console.WriteLine(document.ToJson(new JsonWriterSettings { Indent = true }));
|
}
|
}
|
}
|
No exception is thrown and the document round trips without error. The output of the test program is:
{
|
"_id" : "5c534452d3224cc69bdcb6ac",
|
"Name" : "Centrallagret",
|
"Address" : {
|
"StreetAddress" : "Storgatan 1",
|
"StreetAddress2" : null,
|
"PostalCode" : "123 45",
|
"City" : "Stockholm",
|
"CountryCode" : "se"
|
},
|
"ShelfSettings" : null,
|
"TrolleySettings" : null
|
}
|
Notes:
- I commented out the [JsonConstructor] attribute but it should not be relevant to this test
- I had to adjust the JSON string so that the _id is a string to match the declaration in the class
|
|
Can you confirm that you are using C# 8.0 and the new nullable reference types feature here:
public LocationAddress? Address { get; private set; }
|
I was assuming that `LocationAddress` was a struct and that `LocationAddress?` meant `Nullable<LocationAddress>`.
|
|
The first constructor might look a little weird in isolation, but this type is also being inherited and the subtypes use the first constructor.
|
|
Hi Robert. Certainly, here it is:
public class LocationAddress
|
{
|
public LocationAddress(LocationAddress address)
|
: this(address.StreetAddress, address.StreetAddress2, address.PostalCode, address.City, address.CountryCode) {}
|
|
[JsonConstructor]
|
public LocationAddress(string streetAddress, string? streetAddress2, string postalCode, string city, string countryCode)
|
{
|
this.StreetAddress = streetAddress;
|
this.PostalCode = postalCode;
|
this.City = city;
|
StreetAddress2 = streetAddress2;
|
CountryCode = countryCode;
|
}
|
|
public string StreetAddress { get; private set; }
|
public string? StreetAddress2 { get; private set; }
|
public string PostalCode { get; private set; }
|
public string City { get; private set; }
|
|
/// <summary>
|
/// Two-letter
|
/// </summary>
|
public string CountryCode { get; private set; }
|
}
|
The reason for the [JsonConstructor] attribute is that I also serialize and deserialize this type in order to send it over a message queue.
|
|
Can you please provide the source code for the LocationAddress class and I will attempt to reproduce? Thanks.
|
|
No I don't think it is. The persisted document in my case has all the data needed to call any of the constructors.
|
|
This looks like a duplicate of CSHARP-3108, which has been closed as Works as Designed.
|
Generated at Wed Feb 07 21:44:35 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.