mongodbgeojsonmongodb-.net-driver

Specifing the MapCreator for a model constructor in MongoDB C# still results in an error


I'm trying to store an object to a MongoDB collection using the MongoDB C# driver and using the GeoJSON .Net library on my model, instead of the MongoDB GeoJSON classes (I don't want implementation specific details on my models, and as both should be using the GeoJSON standard, the property names should match).

When I try and save the model to the collection, I get the following error:

An error occurred while serializing the Coordinates property of class GeoJSON.Text.Geometry.LineString: Creator map for class GeoJSON.Text.Geometry.Position has 3 arguments, but none are configured.

Reading the documentation - https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/serialization/class-mapping/#mapping-with-constructors - it seems I need to configure the BSON Class Map, which I have done as below, however I'm still getting that error message. Am I missing something else in terms of registering the classes for MongoDB serialisation? Is my understanding of this not correct and the MapCreator is not the mapping method to use for this?

BsonClassMap.RegisterClassMap<Position>(cm =>
{
    cm.AutoMap();
    cm.MapCreator(p => new Position(p.Longitude, p.Latitude, p.Altitude));
});

Edit: additional code

public class PaddockView : IEntity
{
    public Guid Id { get; set; }
    public DateTime Created { get; set; }
    public DateTime Updated { get; set; }
    public Guid FarmId { get; set; }
    public string Name { get; set; } = "";
    public string Description { get; set; } = "";
    public PaddockType PaddockType { get; set; }

    public Polygon Geometry { get; set; } = new (new List<LineString>
     {
         new LineString(new List<Position>
         {
             new Position(0, 0),
             new Position(0, 1),
             new Position(1, 1),
             new Position(1, 0),
             new Position(0, 0)  // Closing the loop (same as the first position)
         })
     });
}

And the mongo initialisation code:

BsonClassMap.TryRegisterClassMap<Position>(cm =>
        {
            cm.AutoMap();
            cm.MapCreator(p => new Position(p.Longitude, p.Latitude, p.Altitude));
        });
        
        // Determine the GUID serialization mode
        var objectSerializer = new ObjectSerializer(ObjectSerializer.AllAllowedTypes);
        BsonSerializer.TryRegisterSerializer(objectSerializer);
        BsonSerializer.TryRegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));  

var mongoUrl = MongoUrl.Create(connectionString);

        var client = new MongoClient(mongoUrl);
        var db = client.GetDatabase(mongoUrl.DatabaseName);

// get collection and save object occurs after this.

Solution

  • Probably there is more general approach, but the below fixes your issue:

            BsonClassMap.RegisterClassMap<Position>(cm =>
            {
                cm.AutoMap();
                var ctors = typeof(Position).GetConstructors();
                cm.UnmapConstructor(ctors.Last()); // last is about ctor with string arguments
            });
    
            var objectSerializer = new ObjectSerializer(type =>
                ObjectSerializer.DefaultAllowedTypes(type) || type == typeof(Position));
            BsonSerializer.RegisterSerializer(objectSerializer);
    
            var res = obj.ToBsonDocument();
    

    This approach removes the problematic ctor with string arguments from considered ctors. To avoid this error, the ctor should have the same parameters as properties in the class with matching names and types (where types are not the same in this case). I think there is a more elegant approach, but I didn't work with this long time to do it quickly

    UPDATE: Still probably not ideal, but a bit more general approach:

            BsonClassMap.RegisterClassMap<Position>(cm =>
            {
                cm.AutoMap();
                var type = typeof(Position);
                var ctors = type.GetConstructors();
                var members = type.GetMembers();
    
                foreach (var ctor in ctors)
                {
                    var firstMembers = members
                        .Where(m => ctor.GetParameters().Select(p => p.Name).Contains(m.Name, StringComparer.OrdinalIgnoreCase));
                    cm
                        .CreatorMaps.Single(i => i.MemberInfo == ctor)
                        .SetArguments(firstMembers);
                }
            });