Update
Here's some more information about my application business logic, based on @aman's observation that "it should be possible for you to pull out those elements from the dictionary that your business code depends on and have them represented by their well-defined properties".
The relevant part of the application is a registry of (financial) products. The application allows power users to define additional fields on the products and also define limited business logic. E.g. they may define a field called RiskValue
of type decimal
and a RiskColor
of type string
and a piece of business logic that could say
If RiskColor is greater than 1.5 then RiskColor should be "Red"
Now, this business logic needs to be able to cast RiskColor
to decimal
in order to perform the comparison and that's where the problem arises.
Based on the definitions above, it would be possible to generate a class at runtime, something like:
public class Product
{
public decimal RiskValue { get; set; }
public string RiskColor { get; set; }
public void BusinessLogic( )
{
if( RiskValue > 1.5M)
RiskColor = "Red";
}
But I'm not sure I want to go that route.
I have some model classes (in C#) where some properties are not known at compile time. So I have a
[BsonExtraElements]
IDictionary<string,object> ExtraElements {get;}
(actually BsonExtraElements comes through a convention, but I don't think that matters).
Some of these properties are of type System.Decimal
, e.g.
obj.ExtraElements["PropertyX"] = 1.000M;
When I save the object to MongoDB, it gets serialized like this:
{
...
"PropertyX" : NumberDecimal("1.000"),
}
which is what I expect.
However, when I read the object back into my model, the value of the property is of type MongoDB.Bson.Decimal128
instead of the expected System.Decimal
.
Most of the application (except the data layer) is agnostic about the underlying storage mechanism, which is also why I am registering the [BsonExtraElements]
via a convention instead of via the attribute (the assembly containing my model doesn't have a reference to the MongoDB driver). I'm looking for a solution where I don't need to litter my code with MongoDB specific code. I'm hoping there is a flag I missed, or perhaps some way to make a custom convention to fix it at the driver level.
For now I have solved the issue by implementing IDictionary<string,object>
in a class called ExtraElementsDictionary
and passing the value through this method on every action:
private static object FixValue( object value )
{
if( value == null ) return null;
if( value.GetType( ).FullName == "MongoDB.Bson.Decimal128" )
{
return decimal.TryParse( value.ToString( ), out var dec ) ? dec : default;
}
return value;
}
I also considered doing it at the data layer, something like
public MyModel GetMyModelById(string id)
{
// actual retrieval of object
FixDecimal128(obj.ExtraElements);
return obj;
}
But there must be a better way?
Update:
Since you do not want the business logic to become aware of the underlying types that your database library uses, I imagine that you also have concretely defined use for those values for which such a stipulation exists otherwise it would not make much sense.
In such a case, it should be possible for you to pull out those elements from the dictionary that your business code depends on and have them represented by their well-defined properties. In my view, that would be the cleanest approach:
public decimal ValueThatMyBusinessCodeUses { get; set; }
BsonSerializer.RegisterSerializer<IDictionary<string, object>>(new CustomDictionarySerializer());
In regards to how to implement your dictionary serializer, have a look at the MongoDB documentation on Serialization.
There are various helper methods in the MongoDB library that allow casting between the C# types and the BSON class types that MongoDB uses.
If you have a MongoDB.Bson.Decimal128
based value, you can simply cast it to decimal as follows:
Decimal128 value = 2.4M;
decimal result = (decimal)value;
If you have a MongoDB.Bson.BsonDecimal128
based value, you can do the following:
BsonDecimal128 value = new BsonDecimal128(2.4M);
decimal result = value.AsDecimal;