We are using MongoDb to store product attributes and have created a custom array serializer to take a dictionary and serialize it in a custom format. The format takes the key and value of each dictionary item and stores it as a single array item separated with a colon.
Example product class
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
[BsonSerializer(typeof(ProductAttributeSerializer))]
public Dictionary<string, string> Attributes { get; set; } = new Dictionary<string, string>();
}
Example data with custom array format
{
"_id" : ObjectId("56d94ee01992d148b4c10d59"),
"Name" : "My Product",
"Attributes" : [
"Colour:Blue",
"Gender:Mens"
]
}
Here is our serializer
[BsonSerializer(typeof(ProductAttributeSerializer))]
public class ProductAttributeSerializer : IBsonSerializer, IBsonArraySerializer
{
public Type ValueType { get { return typeof(Dictionary<string, string>); } }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var type = context.Reader.GetCurrentBsonType();
var attributes = new Dictionary<string, string>();
switch (type)
{
case BsonType.Array:
context.Reader.ReadStartArray();
while (context.Reader.ReadBsonType() != BsonType.EndOfDocument)
{
string[] splitValues = context.Reader.ReadString().Split(':');
attributes.Add(splitValues[0], splitValues[1]);
}
context.Reader.ReadEndArray();
return attributes;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var attributes = value as Dictionary<string, string>;
if (attributes != null)
{
context.Writer.WriteStartArray();
foreach (KeyValuePair<string, string> attr in attributes)
{
context.Writer.WriteString($"{attr.Key}:{attr.Value}");
}
context.Writer.WriteEndArray();
}
}
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
{
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
}
All of the serialization of items works, however when trying to query the data through the driver and trying to do something equivalent to this db.Product.find({ Attributes : { $in : [ "Colour:Blue" ] } })
I am getting an error.
The original error indicated that the serializer needed to be an IBsonArraySerializer
, now that is the case I need to implement the TryGetItemSerializationInfo
method.
Based on the example above, what should this method do and set as its output?
I have tried looking up a string serializer and using that but it gives an error saying "Unable to cast object of type 'System.Char' to type 'System.String'". Trying a char serializer does not give an error but does not return any results. Looking in the logs for MongoDb seems to indicate the search was converted character by character to integers and then searched upon.
The code for the filter statement is also below
Database.GetCollection<Product>("Product").Find(Builders<Product>.Filter.AnyIn("Attributes", "Colour:Blue")).ToListAsync()