I have four simple classes
public class Zoo{
public ObjectId Id { get; set; }
public List<Animal> Animals { get; set; }
}
public class Animal{
public ObjectId Id { get; set; }
public string Name { get; set; }
}
public class Tiger : Animal{
public double Height { get; set; }
}
public class Zebra : Animal{
public long StripesAmount { get; set; }
}
I have created the custom serializer which allows me to store Animal object in a distinct collection ("animals").
class MyAnimalSerializer : SerializerBase<Animal>
{
public override void Serialize(MongoDB.Bson.Serialization.BsonSerializationContext context, MongoDB.Bson.Serialization.BsonSerializationArgs args, Animal value)
{
context.Writer.WriteStartDocument();
context.Writer.WriteName("_id");
context.Writer.WriteObjectId(ObjectId.GenerateNewId());
context.Writer.WriteName("_t");
context.Writer.WriteString(value.GetType().Name);
context.Writer.WriteName("Name");
context.Writer.WriteString(value.Name);
switch (value.AnimalType)
{
case AnimalType.Tiger:
context.Writer.WriteName("Height");
context.Writer.WriteDouble((value as Tiger).Height);
break;
case AnimalType.Zebra:
context.Writer.WriteName("StripesAmount");
context.Writer.WriteInt32((value as Zebra).StripesAmount);
break;
default:
break;
}
context.Writer.WriteEndDocument();
}
public override Animal Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args)
{
context.Reader.ReadStartDocument();
ObjectId id = context.Reader.ReadObjectId();
string object_type = context.Reader.ReadString();
string animal_name = context.Reader.ReadString();
switch (object_type)
{
case "Tiger":
double tiger_height = context.Reader.ReadDouble();
context.Reader.ReadEndDocument();
return new Tiger()
{
Id = id,
Name = animal_name,
Height = tiger_height
};
default:
long zebra_stripes = context.Reader.ReadInt64();
context.Reader.ReadEndDocument();
return new Zebra()
{
Id = id,
Name = animal_name,
StripesAmount = zebra_stripes
};
}
return null;
}
}
Which works well and also allows me things like that:
MongoDB.Bson.Serialization.BsonSerializer.RegisterSerializer(typeof(Animal), new MyAnimalSerializer());
IMongoCollection<Animal> collection = db.GetCollection<Animal>("animals");
var lst = await collection.Find<Animal>(new BsonDocument()).ToListAsync();
But I cannot do the same, when Animals are stored in Zoo and cannot deserialize Zoo from Zoo collection:
IMongoCollection<Zoo> collection = db.GetCollection<Zoo>("zoocollection");
var lst = await collection.Find<Zoo>(new BsonDocument()).ToListAsync(); //not working here
Is it possible to create custom collection serializer for the field?
public List<Animal> Animals { get; set; }
Could anyone give an example? Thanks in advance.
Have you visited this document page? There are also examples for polymorphic classes.
Here is my example of storing objects:
public class Zoo
{
[BsonId]
public ObjectId Id { get; set; }
public List<Animal> Animals { get; set; }
}
[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Tiger), typeof(Zebra))]
public class Animal
{
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
}
public class Tiger : Animal
{
public double Height { get; set; }
}
public class Zebra : Animal
{
public long StripesAmount { get; set; }
}
public class MongoDocumentsDatabase
{
/// <summary>
/// MongoDB Server
/// </summary>
private readonly MongoClient _client;
/// <summary>
/// Name of database
/// </summary>
private readonly string _databaseName;
public MongoUrl MongoUrl { get; private set; }
/// <summary>
/// Opens connection to MongoDB Server
/// </summary>
public MongoDocumentsDatabase(String connectionString)
{
MongoUrl = MongoUrl.Create(connectionString);
_databaseName = MongoUrl.DatabaseName;
_client = new MongoClient(connectionString);
}
/// <summary>
/// Get database
/// </summary>
public IMongoDatabase Database
{
get { return _client.GetDatabase(_databaseName); }
}
public IMongoCollection<Zoo> Zoo { get { return Database.GetCollection<Zoo>("zoo"); } }
}
class Program
{
static void Main(string[] args)
{
var connectionString =
"mongodb://admin:admin@localhost:27017/testDatabase";
var pr = new Program();
pr.Save(connectionString);
var zoo = pr.Get(connectionString);
foreach (var animal in zoo.Animals)
{
Console.WriteLine(animal.Name + " " + animal.GetType());
}
}
public void Save(string connectionString)
{
var zoo = new Zoo
{
Animals = new List<Animal>
{
new Tiger
{
Height = 1,
Name = "Tiger1"
},
new Zebra
{
Name = "Zebra1",
StripesAmount = 100
}
}
};
var database = new MongoDocumentsDatabase(connectionString);
database.Zoo.InsertOneAsync(zoo).Wait();
}
public Zoo Get(string connectionString)
{
var database = new MongoDocumentsDatabase(connectionString);
var task = database.Zoo.Find(e => true).SingleAsync();
task.Wait();
return task.Result;
}
}
Here is how objects were stored in database (Robomongo)
And end result :
Many thanks Anton Putau for the simplest possible solution.
But there is another one. To serialize objects manually:
public class MyListAnimalSerializer : SerializerBase<List<Animals>>
{
public override void Serialize(MongoDB.Bson.Serialization.BsonSerializationContext context, MongoDB.Bson.Serialization.BsonSerializationArgs args, List<Animal> value)
{
context.Writer.WriteStartArray();
foreach (Animal mvnt in value)
{
context.Writer.WriteStartDocument();
switch (mvnt.GetType().Name)
{
case "Tiger":
//your serialization here
break;
case "Zebra":
//your serialization here
break;
default:
break;
}
context.Writer.WriteEndDocument();
}
context.Writer.WriteEndArray();
}
public override List<Animals> Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args)
{
context.Reader.ReadStartArray();
List<Animals> result = new List<Animals>();
while (true)
{
try
{
//this catch block only need to identify the end of the Array
context.Reader.ReadStartDocument();
}
catch (Exception exp)
{
context.Reader.ReadEndArray();
break;
}
var type = context.Reader.ReadString();
var _id = context.Reader.ReadObjectId();
var name = context.Reader.ReadString();
if (type == "Tiger")
{
double tiger_height = context.Reader.ReadDouble();
result.Add(new Tiger()
{
Id = id,
Name = animal_name,
Height = tiger_height
});
}
else
{
long zebra_stripes = context.Reader.ReadInt64();
result.Add(return new Zebra()
{
Id = id,
Name = animal_name,
StripesAmount = zebra_stripes
});
}
context.Reader.ReadEndDocument();
}
return result;
}
}
And just you have to annotate the IEnumerable field to use your serializer:
[BsonSerializer(typeof(MyListAnimalSerializer))]
public List<Animal> Animals { get; set; }
Try this implementation of Deserialize. It will avoid the try ... catch implementation.
public override List<Animals> Deserialize(MongoDB.Bson.Serialization.BsonDeserializationContext context, MongoDB.Bson.Serialization.BsonDeserializationArgs args)
{
context.Reader.ReadStartArray();
context.Reader.ReadBSonType();
List<Animals> result = new List<Animals>();
while (context.Reader.State != MongoDB.Bson.IO.BsonReaderState.EndOfArray)
{
context.Reader.ReadStartDocument();
var type = context.Reader.ReadString();
var _id = context.Reader.ReadObjectId();
var name = context.Reader.ReadString();
if (type == "Tiger")
{
double tiger_height = context.Reader.ReadDouble();
result.Add(new Tiger()
{
Id = id,
Name = animal_name,
Height = tiger_height
});
}
else
{
long zebra_stripes = context.Reader.ReadInt64();
result.Add(return new Zebra()
{
Id = id,
Name = animal_name,
StripesAmount = zebra_stripes
});
}
context.Reader.ReadEndDocument();
context.Reader.ReadBsonType();
}
context.Reader.ReadEndArray();
return result;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With