How to use decimal with mongodb and .NET C#
When working with money we need to be concerned with the accuracy of the data as well as the calculations we do. Imagine that you have an ecommerce website with a feature that allows users to have a credit balance on their accounts. One user has 86.25€ and had just spend 86.24€. We expect him to be left with 0.01. But is he?
It will depend on the data type we are using to store the money. To give you an example, I have prepared a small piece of code.
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
double a = 86.25;
double c = 86.24;
Console.WriteLine(a - c);
}
}
}
The output of this code will be:
0.0100000000000051
Surprised? I hope not.
So, to deal with money in .NET we should use decimal or apply the money pattern.
So far so good.
I have recently started a personal project and I chose to give MongoDB a try. In this project I will work with money and naturally I started using decimal
for it. Unfortunally, when I started implementing the data access layer I realized MongoDB didn’t support decimal
. MongoDB does support double
, however it will not have the expected behaviour as we saw previous in this post.
To solve this, first, I changed all my code to use Int64
instead of decimal
. I thought it would work just fine and it did work, but the quality of the code wasn’t the best… and I was doing a modification on my domain based on my data layer. Definitely, not a good thing to do.
So, I decided to create a custom serializer for the fields of type decimal
.
[BsonSerializer(typeof(MongoDbDecimalFieldSerializer))]
public class MongoDbDecimalFieldSerializer : SerializerBase<decimal>
{
const decimal DECIMAL_PLACE = 10000m;
public override decimal Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var dbData = context.Reader.ReadInt64();
return Math.Round((decimal)dbData / DECIMAL_PLACE, 4);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, decimal value)
{
var realValue = (decimal)value;
context.Writer.WriteInt64(Convert.ToInt32(realValue * DECIMAL_PLACE));
}
}
To register it I have just done:
BsonSerializer.RegisterSerializer(typeof(decimal), new MongoDbDecimalFieldSerializer());
And with this, I have a good code, easy to maintain and the accuracy I need using MongoDB.
Member discussion