Difference between [BsonId] and BsonClassMap.MapIdField - c#

Using the C# MongoDB Driver, I'm trying to understand the main difference between the Attribute [BsonId] and the BsonClassMap.MapIdField method used in BsonClassMap.RegisterClassMap.
Less generally I'm trying to use a string field of a Model as my document's Id field. If I use the [BsonId] Attribute everything is working fine:
public class MyModel
{
[BsonId]
public string AStringId{ get; set; }
}
But if I try to instead register it as Id with MapIdField I then have a System.FormatException: Cannot deserialize a 'String' from BsonType 'ObjectId'.. whenever I'm querying that Collection 🙈
BsonClassMap.RegisterClassMap<MyModel>(cm =>
{
cm.MapIdField(m => m.AStringId);
});
I'd like to know why this problem is happening and more generally what's the difference between those two ways to use a field as custom Id?
🙏 : Don't RTFD me as the MongoDB C# driver documentation is awfully dry on that subject
Thanks

Related

Plugin.CloudFirestore.Attribute converting a list of enums to their name value in Firestore

I am using the Plugin.CloudFirestore library for Xamarin. I have a model and would like to convert the enum to words when saving to my Firestore account. This works fine in most cases. The case I am having problems with is when I have a List of enums in my model. In this case I see a list of integers when I look at the Firestore database.
Here is a piece of my model class:
public class ExampleModel
{
[MapTo("name")]
public string Name { get; set; }
// This maps correctly with the name value of the enum
[MapTo("geneticType")]
[DocumentConverter(typeof(EnumStringConverter))]
public GKind GeneticType { get; set; }
// The list of these enums is not named correctly. The integer
// value of enum is shown in the list in the firestore database
[MapTo("produces")]
[DocumentConverter(typeof(EnumStringConverter))]
public List<UseKind> Produces { get; set; }
So how can I do this? I am thinking I need to pass in a second parameter into the DocumentConverter but I am unable to find any examples online on how to do this.
Thanks.

How to do mongoDB migration when model was a concrete type and now is an interface

I am using MongoDB C# driver version 2.4.4.
I am having problems migrating old documents that were based in a concrete type to a new model, that is based on interface.
At the beginning the model was something like:
public class DeliveryRoute : MongoDbItem
{
...
public List<Step> Steps { get; set; }
...
}
Now, I needed to chance to an interface, and new code looks like:
public class DeliveryRoute : MongoDbItem
{
...
public List<IStep> Steps { get; set; }
...
}
When I save new documents, MongoDB Driver saves a field named "_t" that contains the concrete type.
The problems is that I already have some documents that don't have this field.
I thought that solution was easy, then I created this script to add the new field for every collection that I have:
db.getCollection('MongoDBDeliveryRouteCollection').find({"UnitCode":"016302", "_id":"01630220181030013818"})
.forEach(function(deliveryRouteDocument){
deliveryRouteDocument.Steps.forEach(function(step){
if(!step.hasOwnProperty('_t')){
step._t = "Step";
}
});
db.getCollection('MongoDBDeliveryRouteCollection').save(deliveryRouteDocument);
})
The field was created.
But seems that the driver is ignoring the "_t" when it is created by the script.
Resulting in the error:
Unable to determine actual type of object to deserialize for interface
type PathTracker.Common.Interfaces.IStep.
Somebody has some idea what am I doing wrong?

mongoDB, strongly typed collection using C# oficial driver

I'm learning MongoDB and I want to try it with using C#. Is it possible to operate on strongly typed MongoDB documents using C# official MongoDB driver?
I have classes Album and Photo:
public class Album : IEnumerable<Photo>
{
[Required]
[BsonElement("name")]
public string Name { get; set; }
[Required]
[BsonElement("description")]
public string Description { get; set; }
[BsonElement("owner")]
public string Owner { get; set; }
[BsonElement("title_photo")]
public Photo TitlePhoto { get; set; }
[BsonElement("pictures")]
public List<Photo> Pictures { get; set; }
//rest of the class code
}
public class Photo : IEquatable<Photo>
{
[BsonElement("name")]
public string Name;
[BsonElement("description")]
public string Description;
[BsonElement("path")]
public string ServerPath;
//rest of the class code
}
I want to insert a new document into a collection albums in the test database. I don't want to operate on BsonDocument, but I would prefer to use strongly typed Album. I thought it would be something like:
IMongoClient client = new MongoClient();
IMongoDatabase db = client.GetDatabase("test");
IMongoCollection<Album> collection = database.GetCollection<Album>("album");
var document = new Album
{
Name = album.Name,
Owner = HttpContext.Current.User.Identity.Name,
Description = album.Description,
TitlePhoto = album.TitlePhoto,
Pictures = album.Pictures
};
collection.InsertOne(document);
But it gives me the following error:
An exception of type 'MongoDB.Driver.MongoCommandException' occurred
in MongoDB.Driver.Core.dll but was not handled in user code
Additional information: Command insert failed: error parsing element 0
of field documents :: caused by :: wrong type for '0' field, expected
object, found 0: [].
What am I doing wrong and if it's possible to achieve?
It looks like the driver is treating your object as a BSON array because it implements IEnumerable<Photo>. The database is expecting a BSON document instead. You'll get a similar error if you try to insert, for example, an Int32 into a collection.
Unfortunately, I don't know how to configure the serializer to treat your Album object as a BSON document. The static BsonSerializer.SerializerRegistry property shows the driver is choosing to use EnumerableInterfaceImplementerSerializer<Album,Photo> as Album's serializer by default.
Removing the IEnumerable<Photo> implementation from Album causes the driver to serialize with BsonClassMapSerializer<Album>, producing a BSON document. While it works, the downside is Album is no longer enumerable; application consumers will need to enumerate the Pictures property.
Adding the IEnumerable<Photo> implementation back in, then forcing the aforementioned serializer (using the [BsonSerializer(typeof(BsonClassMapSerializer<Album>))] attribute) results in:
System.MissingMethodException: No parameterless constructor defined for this object.
Based on the stack trace (referring to BsonSerializerAttribute.CreateSerializer), the object the message refers to appears to be something serialization-related, not the data objects themselves (I defined parameterless constructors for both). I don't know if there's a way around this problem with further configuration, or if the driver just won't allow an IEnumerable to be used this way.

MongoDB C# Driver - Ignore fields on binding

When using a FindOne() using MongoDB and C#, is there a way to ignore fields not found in the object?
EG, example model.
public class UserModel
{
public ObjectId id { get; set; }
public string Email { get; set; }
}
Now we also store a password in the MongoDB collection, but do not want to bind it to out object above. When we do a Get like so,
var query = Query<UserModel>.EQ(e => e.Email, model.Email);
var entity = usersCollection.FindOne(query);
We get the following error
Element 'Password' does not match any field or property of class
Is there anyway to tell Mongo to ignore fields it cant match with the models?
Yes. Just decorate your UserModel class with the BsonIgnoreExtraElements attribute:
[BsonIgnoreExtraElements]
public class UserModel
{
public ObjectId id { get; set; }
public string Email { get; set; }
}
As the name suggests, the driver would ignore any extra fields instead of throwing an exception. More information here - Ignoring Extra Elements.
Yet Another possible solution, is to register a convention for this.
This way, we do not have to annotate all classes with [BsonIgnoreExtraElements].
Somewhere when creating the mongo client, setup the following:
var pack = new ConventionPack();
pack.Add(new IgnoreExtraElementsConvention(true));
ConventionRegistry.Register("My Solution Conventions", pack, t => true);
Yes. Another way (instead of editing you model class) is to use RegisterClassMap with SetIgnoreExtraElements.
In your case just add this code when you initialize your driver:
BsonClassMap.RegisterClassMap<UserModel>(cm =>
{
cm.AutoMap();
cm.SetIgnoreExtraElements(true);
});
You can read more about ignoring extra elements using class mapping here - Ignoring Extra Elements.

Mongo C# Driver and ObjectID JSON String Format

Is it possible to force the JsonWriterSettings to output the ObjectID as
{ "id" : "522100a417b86c8254fd4a06" }
instead of
{ "_id" : { "$oid" : "522100a417b86c8254fd4a06" }
I know I could write my own parser, but for the sake of code maintenance, I would like to find away to possibly override the Mongo JsonWriterSettings.
If this is possible, what classes/Interfaces should I override?
If you're OK with using MongoDB C# attributes or the Mapper, then you can do something like this:
public class Order {
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
}
That way, you can refer to the type as a string normally (including serialization), but when MongoDB serializes it, etc., it's internally treated as an ObjectId. Here's using the class map technique:
BsonClassMap.RegisterClassMap<Order>(cm => {
cm.AutoMap();
cm.SetIdMember(cm.GetMemberMap(c => c.Id);
cm.GetMemberMap(c => c.Id)
.SetRepresentation(BsonType.ObjectId);
});
If you use JSON.NET instead it's easy to add a JsonConverter that converts ObjectId values to strings and vice-versa.
In ASP.NET WebAPI you can then add this to the default set of converters at Formatters.JsonFormatter.SerializerSettings.Converters
I am using MongoDB.Driver with version 2.15.1 and for me is working this simple solution:
public class Order {
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
}
You do not have to specify attribute [BsonId] if the property name is "Id" The driver uses naming conventions.

Categories