Question

I am helping to write a C# application to analyse financial data. Internally all the numbers are stored as decimal, however when we persist them to our mongodb database, we would like them to be stored as doubles, rather than strings. I know I can do this for individual fields by applying the [BsonRepresentation(BsonType.Double)] attribute to each decimal field, but I keep forgetting when adding new decimal values.

Is there any way of making this the default representation for all decimal numbers?

Was it helpful?

Solution

For anyone coming to this question now, it's worth mentioning that since v3.4, Mongo now natively supports storing Decimal values, which is the best way to serialize C# decimal values since using Double can result in loss of precision, which can be particularly problematic with financial data.

You can find information on how to do this here: How to use decimal type in MongoDB

OTHER TIPS

You can see my pull request for a sample of a convention to do this

https://github.com/mongodb/mongo-csharp-driver/pull/175

Here's how you would register it:

var conventions = new ConventionPack
{
    new DecimalRepresentationConvention(BsonType.Double)
};
ConventionRegistry.Register("decimalAsDouble", conventions, t => condition);

For they type condition, people usually check the namespace, if it's just your own types you are serializing, you could just return true to apply it to every type.

See more about conventions here:

http://docs.mongodb.org/ecosystem/tutorial/serialize-documents-with-the-csharp-driver/#conventions

Looks like I'm talking to myself, but it might help someone else...

Here is an easier way:

void Main()
{
    BsonSerializer.RegisterSerializationProvider(new MyDecimalSerializer());
    Console.WriteLine(new Test().ToJson(new JsonWriterSettings() { Indent = true }));
}

class MyDecimalSerializer : DecimalSerializer, IBsonSerializationProvider {
    private IBsonSerializationOptions _defaultSerializationOptions = new RepresentationSerializationOptions(BsonType.Double);

    public override void Serialize(
        BsonWriter bsonWriter,
        Type nominalType,
        object value,
        IBsonSerializationOptions options) {
        if(options == null) options = _defaultSerializationOptions;
        base.Serialize(bsonWriter, nominalType, value, options);
    }

    public IBsonSerializer GetSerializer(Type type) {
        return type == typeof(Decimal) ? this : null;
    }
}

Using the same Test class as my other answer above, this works.

For simplicity, I have made my decimal serializer an IBsonSerialisationProvider itself - in the Bson source code, that role is usually taken by a class that maintains a list of types and serializers that can handle them.

Based on @NikkiLocke answer, but working on newer versions of mongo c# driver:

public class BsonDecimalSerializer : DecimalSerializer, IBsonSerializationProvider
{
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, decimal value)
    {
        context.Writer.WriteDouble((double)value);
    }
    public override decimal Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        return (decimal)context.Reader.ReadDouble();
    }
    public IBsonSerializer GetSerializer(Type type)
    {
        return type == typeof(decimal) ? this : null;
    }
}

And the serializer registration:

BsonSerializer.RegisterSerializationProvider(new BsonDecimalSerializer());

I have an answer, but it ain't pretty - better suggestions still welcome.

var conventions = new ConventionPack();
var representAsDouble = new RepresentationSerializationOptions(BsonType.Double);
var representArrayAsDouble = new ArraySerializationOptions(representAsDouble);
var representDictionaryAsDouble = new DictionarySerializationOptions(){ KeyValuePairSerializationOptions = new KeyValuePairSerializationOptions() { ValueSerializationOptions = representArrayAsDouble}};
conventions.Add(new MemberSerializationOptionsConvention(typeof(decimal), representAsDouble));
conventions.Add(new MemberSerializationOptionsConvention(typeof(decimal[]), representArrayAsDouble));
conventions.Add(new MemberSerializationOptionsConvention(typeof(Dictionary<string, decimal[]>), representDictionaryAsDouble));
conventions.Add(new MemberSerializationOptionsConvention(typeof(SortedDictionary<string, decimal[]>), representDictionaryAsDouble));
ConventionRegistry.Register("Serialize decimal as double", conventions, t => true);

If this code is run before any serialization, it serializes all the given types as doubles. However, it does not seem to deal automatically with types derived from Dictionary - they have to be included in the conventions individually, which may be almost as much work as decorating all the properties with BsonRepresentation attributes. Likewise it doesn't deal with dictionaries inside other dictionaries.

Here is my test code:

void Main() {
    var conventions = new ConventionPack();
    var representAsDouble = new RepresentationSerializationOptions(BsonType.Double);
    var representArrayAsDouble = new ArraySerializationOptions(representAsDouble);
    var representDictionaryAsDouble = new DictionarySerializationOptions(){ KeyValuePairSerializationOptions = new KeyValuePairSerializationOptions() { ValueSerializationOptions = representArrayAsDouble}};
    conventions.Add(new MemberSerializationOptionsConvention(typeof(decimal), representAsDouble));
    conventions.Add(new MemberSerializationOptionsConvention(typeof(decimal[]), representArrayAsDouble));
    conventions.Add(new MemberSerializationOptionsConvention(typeof(Dictionary<string, decimal[]>), representDictionaryAsDouble));
    conventions.Add(new MemberSerializationOptionsConvention(typeof(SortedDictionary<string, decimal[]>), representDictionaryAsDouble));
    conventions.Add(new MemberSerializationOptionsConvention(typeof(Test.DerivedDictionary), representDictionaryAsDouble));
    ConventionRegistry.Register("Serialize decimal as double", conventions, t => true);
    Console.WriteLine(new Test().ToJson(new JsonWriterSettings() { Indent = true }));

}

class Test : DbObject {
    public Test() {
        Array = new decimal[2];
        Array[0] = 2;
        Array[1] = 3;
        Dict = new Dictionary<string, decimal[]>();
        Dict["test"] = Array;
        Dict2 = new SortedDictionary<string, decimal []>();
        Dict2["test"] = Array;
        Dict3 = new DerivedDictionary();
        Dict3["test"] = Array;
        Dict4 = new Dictionary<string, DerivedDictionary>();
        Dict4["test"] = Dict3;
    }

    public decimal Field = 1;

    public decimal [] Array;

    public Dictionary<string, decimal[]> Dict;

    public SortedDictionary<string, decimal[]> Dict2;

    public DerivedDictionary Dict3;

    public Dictionary<string, DerivedDictionary> Dict4;

    public class DerivedDictionary : Dictionary<string, decimal[]> {
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top