Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I tell the MongoDB C# driver to store all Guids in string format?

I'm currently applying the [BsonRepresentation(BsonType.String)] attribute to all Guid properties in my domain models to have those properties serialized in string format. Besides being tiresome to do, that doesn't work out sometimes, e.g. with custom Wrapper<T> classes:

public class Wrapper<T>
{
    public T Value { get; set; }

    // Further properties / business logic ...
}

When T is Guid, the Value property will be stored as binary data of type UuidLegacy (as will any property of type Guid that's not decorated with the above attribute). However, I'd like all Guids, including Wrapper<Guid>.Value, to be represented as a string in the database.

Is there any way to tell the MongoDB C# driver to store all Guids in string format?

like image 230
Marius Schulz Avatar asked Dec 12 '12 11:12

Marius Schulz


4 Answers

This can be achieved using Conventions

Something along the lines of:

var myConventions = new ConventionProfile();
myConventions.SetSerializationOptionsConvention(
    new TypeRepresentationSerializationOptionsConvention(typeof (Guid), BsonType.String));

BsonClassMap.RegisterConventions(myConventions, t => t == typeof (MyClass));

This should go somewhere in your app startup.

You can read more about conventions here: http://www.mongodb.org/display/DOCS/CSharp+Driver+Serialization+Tutorial#CSharpDriverSerializationTutorial-Conventions

like image 170
Alex Avatar answered Sep 30 '22 06:09

Alex


An alternative to performing this globally without setting a Convention (which I believe is overkill given the actual question was effectively: "how can this be applied globally") can be done by simply calling one line of code:

BsonSerializer.RegisterSerializer(typeof(Guid), 
    new GuidSerializer(BsonType.String));

Just make sure this is the first thing that fires in terms of MongoDb's serialization configuration (even before class maps are registered, the same is true for the convention based solutions posted).

Personally I see this as a better solution than creating a convention with an 'always return true' predicate, that most seem to be suggesting. Conventions are great for more complex scenarios, and for grouping sets of serialization configurations, where the predicate is actually used, and multiple Conventions are bundled into a ConventionPack. But for simply applying a global serialization format, just keep it simple with the above line of code.

If you later decide you have a legitimate Convention that requires a variation of this rule, just register it in order to overwrite the global rule we have set, just as your Convention would overwrite the default global rule, which is set to have Guid's represented as BsonType.Binary. The net result then would be the global rule taking precedence, followed by the Convention, which will overwrite our custom global rule only in such cases where the custom ConventionPack is applicable (based on your custom predicate).

like image 21
AaronHS Avatar answered Sep 30 '22 07:09

AaronHS


While using conventions will work, pay attention to two important (and related) points:

  1. The filter parameter is required, and if the filter is too general (for example: t => true), it can overwrite other registered conventions.
  2. Be aware that the order of registered conventions is important, first register specific filters and after register general conventions.

Another option is to create a BSON Class Map for type Guid, which sets the representation to string:

if (!BsonClassMap.IsClassMapRegistered(typeof(Guid))) {
    BsonClassMap.RegisterClassMap<Guid>(cm => {
        cm.AutoMap();
        cm.Conventions.SetSerializationOptionsConvention(new  TypeRepresentationSerializationOptionsConvention(typeof(Guid), BsonType.String));
    });
}

This should be done before any reading/writing using BsonSerializer, otherwise the default Class Map will be created, and you wont be able to change the Class Map.

like image 34
Schmuli Avatar answered Sep 30 '22 08:09

Schmuli


ConventionProfile was deprecated. If you don't want to apply the rule globally, but only for a specific class (this should go somewhere in your app startup):

var pack = new ConventionPack { new GuidAsStringRepresentationConvention () };
ConventionRegistry.Register("GuidAsString", pack, t => t == typeof (MyClass));

public class GuidAsStringRepresentationConvention : ConventionBase, IMemberMapConvention
    {
        public void Apply(BsonMemberMap memberMap)
        {
            if (memberMap.MemberType == typeof(Guid))
            {
                var serializer = memberMap.GetSerializer();
                var representationConfigurableSerializer = serializer as IRepresentationConfigurable;
                if (representationConfigurableSerializer != null)
                {
                    var reconfiguredSerializer = representationConfigurableSerializer.WithRepresentation(BsonType.String);
                    memberMap.SetSerializer(reconfiguredSerializer);
                }
            }
        }
    }
like image 39
myl Avatar answered Sep 30 '22 07:09

myl