HuseyinUslu
HuseyinUslu

Reputation: 4134

Element 'id' does not match any field or property of error with nested classes

I've the following mongodb document schema;

{ 
    "_id" : ObjectId("5c9d34ff781318afb9e8ab43"), 
    "name" : "Name", 
    "slug" : "slug",
    "services" : {
        "subservice" : {
            "id" : NumberInt(37030)
        }
    }
}

and then i define my classes as;

public class MainModel
{
    public ObjectId Id { get; set; }

    [BsonElement("name")]
    public string Name { get; set; }

    [BsonElement("slug")]
    public string Slug { get; set; }

    [BsonElement("services")]
    public ServicesDef Services { get; set; }

    public class ServicesDef 
    {
        [BsonElement("subservice")]
        public SubServiceDef SubService{ get; set; }

        public class SubServiceDef 
        {
            [BsonElement("id")]
            public int Id { get; set; }
        }
    }
}

But somehow when I query the document;

var result = await Repository.FindAsync(x => x.Slug == slug);

That services.subservice.id isn't properly registered and getting

Element 'id' does not match any field or property of class SubServiceDef.

Stuck here and looking for advice.

I think I'm having the same issue with cannot deserialize with the "Id" attribute but seems there is solution yet.

Upvotes: 6

Views: 7226

Answers (2)

Andrey Borisko
Andrey Borisko

Reputation: 4609

I like the answer from @mickl. The issue i had is couldn't update model and add attributes. Also I needed the original Ids and not nulls after deserialization.

I tried BsonClassMap but i had so many sub models to update.

so, i ended up using your idea with removing default conventions.

public class MongoDbDefaultConventionPack : IConventionPack
{
    // private static fields
    private static readonly IConventionPack __defaultConventionPack = new MongoDbDefaultConventionPack();

    // private fields
    private readonly IEnumerable<IConvention> _conventions;

    // constructors
    /// <summary>
    /// Initializes a new instance of the <see cref="MongoDbDefaultConventionPack" /> class.
    /// </summary>
    private MongoDbDefaultConventionPack()
    {
        _conventions = new List<IConvention>
        {
            new ReadWriteMemberFinderConvention(),
            // new NamedIdMemberConvention(new [] { "Id", "id", "_id" }), changed to:
            new NamedIdMemberConvention(),
            new NamedExtraElementsMemberConvention(new [] { "ExtraElements" }),
            // new IgnoreExtraElementsConvention(false), changed to:
            new IgnoreExtraElementsConvention(true),
            new ImmutableTypeClassMapConvention(),
            new NamedParameterCreatorMapConvention(),
            new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention
            new LookupIdGeneratorConvention()
        };
    }

    // public static properties
    /// <summary>
    /// Gets the instance.
    /// </summary>
    public static IConventionPack Instance
    {
        get { return __defaultConventionPack; }
    }

    // public properties
    /// <summary>
    /// Gets the conventions.
    /// </summary>
    public IEnumerable<IConvention> Conventions
    {
        get { return _conventions; }
    }
}

and then replaced the config:

ConventionRegistry.Remove("__defaults__");
ConventionRegistry.Register("__defaults__", MongoDbDefaultConventionPack.Instance, t => true);

Worked great in my case as default convention. No more exceptions. Original Ids available

Upvotes: 2

mickl
mickl

Reputation: 49975

Long story short: it's all about conventions. MongoDB .NET driver exposes static class ConventionRegistry which allows you to register your own conventions (more here). Additionally there are two "built-in" conventions __defaults__ and __attributes__. Digging deeper (driver github) you can find that it registers one quite interesting convention:

new NamedIdMemberConvention(new [] { "Id", "id", "_id" })

Which means that id members will be considered as regular BSON _id elements.

How to fix that ?

You can get rid of default conventions

ConventionRegistry.Remove("__defaults__");

However automatically you will drop all the other driver conventions which is pretty risky. Alternatively you can create a fake property which will always be empty:

public class SubServiceDef
{
    [BsonElement("id")]
    public int Id { get; set; }

    [BsonId]
    public ObjectId FakeId { get; set; }
}

or you can just use BsonNoId attribute which

Specifies that the class's IdMember should be null.

[BsonNoId]
public class SubServiceDef
{
    [BsonElement("id")]
    public int Id { get; set; }
}

So the convention will be setting your id as IdMember in class map but then during postprocessing this attribute will force IdMember to be null and your class will get deserialized succesfully

Upvotes: 13

Related Questions