Search code examples
c#mongodb.net-coregeojsonmongodb-.net-driver

MongoDB .NET Driver not deserializing for collection


I'm having an issue with deserializing a collection from MongoDB. It seems fine for a single object but fails for a collection of objects. The collection is a GeoJSON object in Mongo with coordinates. That seems to be the problem. Perhaps I'm not representing that right in my C# class. Though it seems to work fine for a single object.

I created a generic collection repo according to this post: Generic Mongo Repository pattern implemented in .NET Core

Given my class:

using System.Collections.Generic;
using MongoDB.Driver.GeoJsonObjectModel;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace VisualStatsPoCAPI.Repositories.Models.Mongo
{
    [BsonCollection("garda_subdistrict_boundaries")]
    public class GardaSubdistrictBoundaryMongo : Document
    {
        [BsonElement("type")]
        public string Type { get; set; }

        [BsonElement("properties")]
        public Properties Properties { get; set; }

        [BsonElement("geometry")]
        public Geometry Geometry { get; set; }
    }

    public class Properties
    {
        public string REGION { get; set; }
        public string REG_CODE { get; set; }
        public string DIVISION { get; set; }
        public string DIV_CODE { get; set; }
        public string DISTRICT { get; set; }
        public string DIST_CODE { get; set; }
        public string SUB_DIST { get; set; }
        public string SUB_IRISH { get; set; }
        public string SUB_CODE { get; set; }
        public string COUNTY_1 { get; set; }
        public string COUNTY_2 { get; set; }
        public string GEOGID { get; set; }
        public int Male2011 { get; set; }
        public int Female2011 { get; set; }
        public int Total2011 { get; set; }
        public int PPOcc2011 { get; set; }
        public int Unocc2011 { get; set; }
        public int Vacant2011 { get; set; }
        public int HS2011 { get; set; }
        public double PCVac2011 { get; set; }
        public string CREATEDBY { get; set; }
    }

    public class Geometry
    {
        [BsonElement("type")]
        public string Type { get; set; }

        [BsonElement("coordinates")]
        public IEnumerable<IEnumerable<GeoJson2DCoordinates>> Coordinates { get; set; }
    }
}

and the MongoDB collection:

enter image description here

and small snippet of the doument itself (which I converted from a Shapefile according to Importing a Shapefile into MongoDB using GeoJSON):

[
    { "type": "Feature", 
      "properties": { 
          "REGION": "Southern Region", 
          "REG_CODE": "03", 
          "DIVISION": "Cork West", 
          "DIV_CODE": "0319", 
          "DISTRICT": "Bandon", 
          "DIST_CODE": "4300A", 
          "SUB_DIST": "Kinsale", 
          "SUB_IRISH": "Cionn tS�ile", 
          "SUB_CODE": "4305B", 
          "COUNTY_1": "Cork", 
          "COUNTY_2": null, 
          "GEOGID": "M4305B", 
          "Male2011": 5765, 
          "Female2011": 5963, 
          "Total2011": 11728, 
          "PPOcc2011": 4054, 
          "Unocc2011": 1177, 
          "Vacant2011": 1013, 
          "HS2011": 5231, 
          "PCVac2011": 19.4, 
          "CREATEDBY": "Paul Creaner" 
       }, 
       "geometry": { 
           "type": "Polygon", 
           "coordinates": [ 
               [ 
                   [-8.665517347801826, 51.701921804534543 ], 
                   [-8.665512199746647, 51.702050730841847 ] 
               ] 
           ] 
        } 
    }
]

I get an error:

System.FormatException: An error occurred while deserializing the Geometry property of class VisualStatsPoCAPI.Repositories.Models.Mongo.GardaSubdistrictBoundaryMongo: An error occurred while deserializing the Coordinates property of class VisualStatsPoCAPI.Repositories.Models.Mongo.Geometry: Cannot deserialize a 'Double' from BsonType 'Array'.

The SDK call I'm using for a single object is:

public virtual TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression)
{
    return _collection.Find(filterExpression).FirstOrDefault();
}

and for a collection, either:

public virtual IEnumerable<TProjected> FilterBy<TProjected>(
    Expression<Func<TDocument, bool>> filterExpression,
    Expression<Func<TDocument, TProjected>> projectionExpression)
{
    return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
}

or

public virtual Task<IEnumerable<TDocument>> FindAll()
{
    FilterDefinition<TDocument> filter = FilterDefinition<TDocument>.Empty;

    return Task.Run(() => _collection.Find(filter).ToList().AsEnumerable());
}

It has something to do with how I'm representing the geometry but I'm unsure. I'm a bit confused. Can anyone help?

Update (25 Mar 2020): It was suggested that I use GeoJsonPolygon. I tried using that like below:

public GeoJsonPolygon<GeoJson2DCoordinates> Geometry { get; set; }

Again, that works fine for a single document. When I try using that for an entire collection, I get:

System.FormatException: An error occurred while deserializing the Geometry property of class VisualStatsPoCAPI.Repositories.Models.Mongo.GardaSubdistrictBoundaryMongo: Invalid GeoJson type: 'MultiPolygon'. Expected: 'Polygon'.

When I switch to using a GeoJsonMultiPolygon (as the compiler suggests), I get:

System.FormatException: An error occurred while deserializing the Geometry property of class VisualStatsPoCAPI.Repositories.Models.Mongo.GardaSubdistrictBoundaryMongo: Invalid GeoJson type: 'Polygon'. Expected: 'MultiPolygon'.


Solution

  • It's unclear from the screenshot and model provided but became clear when you pasted the errors you're getting.

    It looks like your collection contains both Polygons:

    { geometry: { 'type' : 'Polygon', 'coordinates' : [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 0.0]]] } }
    

    and MultiPolygons:

    { geometry: { 'type' : 'MultiPolygon', 'coordinates' : [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]], [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]]] } }
    

    MongoDB .NET driver provides classes for both polygon types (GeoJsonPolygon<TCoordinates>, GeoJsonMultiPolygon<TCoordinates>). Both classes derive from GeoJsonGeometry<GeoJson2DCoordinates>. Furthermore you can use GeoJson2DCoordinates to represent your two-element arrays of doubles.

    The driver will handle the rest - you can specify base abstract type as Geometry and the document will get deserialized to relevant concrete type in the runtime:

    [BsonCollection("garda_subdistrict_boundaries")]
    public class GardaSubdistrictBoundaryMongo : Document
    {
        [BsonElement("type")]
        public string Type { get; set; }
    
        [BsonElement("properties")]
        public Properties Properties { get; set; }
    
        [BsonElement("geometry")]
        public GeoJsonGeometry<GeoJson2DCoordinates> Geometry { get; set; }
    }
    

    enter image description here