silkfire
silkfire

Reputation: 25983

MongoDB: Trouble with child class with an overridden ID and attribute

I'm having big problems when I'm overriding the Id property in a derived class in my MongoDB storage setup.

The base class that all of my data models inherit looks like this:

public abstract class DataModel
{
     [BsonId]
     [BsonRepresentation(BsonType.ObjectId)]
     public virtual string Id { get; set; }

     public DateTime Created { get; set; }

     public DateTime Modified { get; set; }

     public DateTime Deleted { get; set; }
}

Then there are a few particular child data models that use upserts. This requires me to annotate the overridden Id property with [BsonIgnoreIfDefault] as per this answer:

public class ChildDataModel : DataModel
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    [BsonIgnoreIfDefault]          //  <---- need this for Upserts to work
    public override string Id { get; set; }

    ... // child-specific properties
}

But unfortunately it results in this error:

The property 'Id' of type 'Namespace.ChildDataModel' cannot use element name '_id' because it is already being used by property 'Id' of type 'Namespace.DataModel'.

I've tried registering class maps, adding a type discriminator with RootClass = true on the base model, as well as defining my child data models on the base class with the special [BsonKnownTypes(typeof(ChildDataModel), ...)] attribute, but to no avail.

What am I doing wrong here?

Upvotes: 5

Views: 2690

Answers (1)

mickl
mickl

Reputation: 49975

MongoDB driver by convention tries to map all the properties named Id into _id in class map. As you have two classes it registers the _id twice. What's more BsonIgnoreIfDefault would work fine if the Id was null but here it's not since the driver automatically will generate the value when you insert new document.

To fix that you can use BsonIgnore if you want to have single _id in MongoDB

public class ChildDataModel : DataModel
{        
    [BsonRepresentation(BsonType.ObjectId)]
    [BsonIgnore]
    public override string Id { get; set; }
}

will be stored as:

{
    "_id" : ObjectId("5cb5fe72e2a22b3848b6a1f6"),
    "Created" : ISODate("2019-04-16T16:10:25.908Z"),
    "Modified" : ISODate("2019-04-16T16:10:25.914Z"),
    "Deleted" : ISODate("2019-04-16T16:10:25.914Z")
}

or you can use BsonNoId attribute if you want to have two values stored separately:

[BsonNoId]
public class ChildDataModel : DataModel
{        
    [BsonRepresentation(BsonType.ObjectId)]
    public override string Id { get; set; }
}

will be:

{
    "_id" : ObjectId("5cb5fecde2a22b3088ef731c"),
    "Created" : ISODate("2019-04-16T16:11:56.810Z"),
    "Modified" : ISODate("2019-04-16T16:11:56.822Z"),
    "Deleted" : ISODate("2019-04-16T16:11:56.822Z"),
    "Id" : ObjectId("5cb5fecde2a22b3088ef731c")
}

however it's still the same value from application point of view

Upvotes: 5

Related Questions