I have implemented Repository pattern with a base entity class for my collections. Till now all collections had _id
of ObjectId
type. In the code, I needed to represent the Id
as a string.
Here is how the EntityBase
class look like
public abstract class EntityBase
{
[BsonRepresentation(BsonType.ObjectId)]
public virtual string Id { get; set; }
}
Here is the mapping:
BsonClassMap.RegisterClassMap<EntityBase>(cm =>
{
cm.AutoMap();
cm.MapIdProperty(x => x.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
cm.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.ObjectId));
});
Now I have a Language collection which Id will be plain string something like en-GB
.
{
"_id" : "en-GB",
"Term1" : "Translation 1",
"Term2" : "Translation 2"
}
Language
class is inheriting the EntityBase
class
public class Language : EntityBase
{
[BsonExtraElements]
public IDictionary<string, object> Terms { get; set; }
public override string Id { get; set; }
}
The question is can I somehow change how the Id
is serialized only for the Language
class?
I don't want to change the behaviour of EntityBase
class since I have a lot of other collections inheriting the EntityBase
.
Here is what I tried and got exception. Not sure if what I have tried is possible.
BsonClassMap.RegisterClassMap<Language>(cm =>
{
cm.AutoMap();
cm.MapExtraElementsMember(c => c.Terms);
cm.MapIdProperty(x => x.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
cm.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.String));
});
Here is the exception that I was getting:
An exception of type 'System.ArgumentOutOfRangeException' occurred in MongoDB.Bson.dll but was not handled in user code
Additional information: The memberInfo argument must be for class Language, but was for class EntityBase.
at MongoDB.Bson.Serialization.BsonClassMap.EnsureMemberInfoIsForThisClass(MemberInfo memberInfo)
at MongoDB.Bson.Serialization.BsonClassMap.MapMember(MemberInfo memberInfo)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapMember[TMember](Expression`1 memberLambda)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapProperty[TMember](Expression`1 propertyLambda)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapIdProperty[TMember](Expression`1 propertyLambda)
at Test.Utilities.MongoDbClassConfig.<>c.<Configure>b__0_1(BsonClassMap`1 cm) in F:\Development\Test\Utilities\MongoDbClassConfig.cs:line 23
at MongoDB.Bson.Serialization.BsonClassMap`1..ctor(Action`1 classMapInitializer)
at MongoDB.Bson.Serialization.BsonClassMap.RegisterClassMap[TClass](Action`1 classMapInitializer)
at Test.Utilities.MongoDbClassConfig.Configure() in F:\Development\Test\Utilities\MongoDbClassConfig.cs:line 20
at Test.Portal.BackEnd.Startup..ctor(IHostingEnvironment env) in F:\Development\Test\Startup.cs:line 43
Welcome to the documentation site for the official MongoDB C++ driver. You can add the driver to your application to work with MongoDB using the C++11 or later standard.
By developing with C# and MongoDB together one opens up a world of possibilities. Console, window, and web applications are all possible. As are cross-platform mobile applications using the Xamarin framework.
The official MongoDB Node. js driver allows Node. js applications to connect to MongoDB and work with data. The driver features an asynchronous API which allows you to interact with MongoDB using Promises or via traditional callbacks.
I may be late with an answer, but I had the same problem, found this question, but no real answer how to solve and why is it happening.
What is happening is that Mongo driver doesn't explicitly understand polymorphism, and you must manually mention every type. All the types that inherit from the same class must be listed and explained to BsonClassMap
.
In your case when you define your EntityBase you can list all the children that will be serialized and it would get it working. So something along the lines of:
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Language), typeof(OtherEntity))]
public abstract class EntityBase
{
[BsonId]
public virtual string Id { get; set; }
}
should work. In my case, I wanted to have a generic Entity save method and my entities are not aware of the Mongo driver so I could not use attributes. I had my ID in the EntityBase, so here's a simple generic add method (just the relevant, but working part):
public async Task Add<T>(T item) where T : EntityBase, new()
{
BsonClassMap.RegisterClassMap<EntityBase>(cm =>
{
cm.AutoMap();
cm.MapIdMember(p => p.Id);
cm.SetIsRootClass(true);
});
BsonClassMap.RegisterClassMap<T>();
await Db.GetCollection<T>(typeof(T).Name).InsertOneAsync(item);
}
As you can see, I must map the base class with every entry. Then the object is properly serialized and saved in the database.
When you use the attribute [BsonRepresentation(BsonType.ObjectId)]
you are telling the serializer, that even though this is a string — you want it represented by the ObjectId
type. "en-GB"
is not a valid ObjectId
therefor, the serializer throws an exception.
Use the [BsonId]
-attribute if you want to specify that your property named Id
must be a unique identifier within your collection.
There is also an ObjectId
-data type within the driver, which you can implement instead.
I would add a generic type-parameter to your EntityBase
-class.
public abstract class EntityBase<TId>
{
[BsonId]
public TId Id { get; set; }
}
You could also implement a EntityBase
-class, without the generic type argument as such:
public abstract class EntityBase : EntityBase<ObjectId>
{
}
Then for your Language
-class
public class Language : EntityBase<string>
{
[BsonExtraElements]
public IDictionary<string, object> Terms { get; set; }
}
The driver should not have any trouble serializing a string-representation of an ObjectId
to an ObjectId
; but i would try it first — just to make sure.
I have just spent a few hours on this problem and was stumped - but I now have the Solution!! The problem with Gurgen's answer is that SuperBaseEntity.Id
is always null - since the Id Property is overriden. I have managed this:
public abstract class Base
{
[BsonIgnore]
public abstract string Id {get;set}
}
public class ClassWithStringId : Base
{
[BsonElement("_id")]
[BsonId]
public override string Id {get;set;}
}
public classs ClassWIthOidId : Base
{
[BsonElement("_id")]
[BsonId]
public override string Id {get;set;}
}
...
//Class Map where the magic happens
BsonClassMap.RegisterClassMap<Base>(o =>
{
o.SetIsRootClass(true);
o.AutoMap();
}
BsonClassMap.RegisterClassMap<ClassWithOidId>(o =>
{
o.AutoMap();
//o.SetDiscriminator("...")//you probably need this
o.MapIdProperty("Id"); //!!!! This is the solution! You need to reference the Id Property via string instead of the normal Expression c => c.Id, the latter will lead to an exception: "MemberInfo was for Type Base but not for Type XYZ"
o.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.YourFavoriteType));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With