skip to Main Content

I’m working with a system where I save dynamic parameters on machines. This approach is quite convenient for me, and I’d like to continue using it. Here’s a brief overview of my setup:

public List<Machine> Machines { get; set; }

public class Machine
{
    [BsonElement("_id")]
    public string MachineId { get; set; }

    public List<Param> Params { get; set; }
}

public class Param
{
    public int ParamId { get; set; }  
    public object Val { get; set; }
}

I’m using MongoDB with C#, and my Val property in the Param class is meant to store various data types. For example, the Val property could hold:

A complex object:

public class Multi
{
    public int Code1 { get; set; }
    public int Code2 { get; set; }
}

A dictionary:

Dictionary<int, int>

My questions are:

Do I need to create a mapper for each type?
I want to insert values like strings, integers, and complex objects into the Val property without needing a specific mapper for each type.

Are there any best practices for managing such dynamic data in MongoDB with C#?`

2

Answers


  1. You could use BsonDocument type and use .ToBsonDocument() to save any dynamic object.

        public class Param
        {
            public int ParamId { get; set; }
            public BsonDocument Val { get; set; }
        }
    

    One thing to mention that MongoDb.Driver only support string keys. So Dictionary<int, int> is special, you have to convert it to Dictionary<string, int>; such as following sample.

    var mongoClient = new MongoClient("xxx");
    var mongoDatabase = mongoClient.GetDatabase("test");
    var collection = mongoDatabase.GetCollection<Machine>("Machines");
    
    var machine1 = new Machine()
    {
        MachineId = "id1",
        Params = new List<ConsoleApp60.Param>
        {      
            new ConsoleApp60.Param
            {
                ParamId=1,
                Val=(new Multi{ Code1=11,Code2=22 }).ToBsonDocument()
            }
        }
    };
    
    Dictionary<int, int> intDict = new Dictionary<int, int> { { 1, 1 }, { 2, 2 } };
    Dictionary<string, int> stringDict = intDict.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value);
    
    var machine2 = new Machine()
    {
        MachineId = "id2",
        Params = new List<ConsoleApp60.Param>
        {
            new ConsoleApp60.Param
            {
                ParamId=2,
                Val=stringDict.ToBsonDocument()
            }
        }
    };
    
    await collection.InsertOneAsync(machine1);
    await collection.InsertOneAsync(machine2);
    

    Test result
    enter image description here
    enter image description here

    To retrive object

    var machine = await collection.Find(x => x.MachineId == "id1").FirstOrDefaultAsync();
    Multi val1 = BsonSerializer.Deserialize<Multi>(machine.Params[0].Val);
    
    Login or Signup to reply.
  2. No, you don’t need to create a mapper for each type. MongoDB and the C# driver can handle dynamic types using Bson serialization, specifically the BsonValue or object type.

    Since you are using the object type for the Val property, MongoDB will serialize and deserialize the data based on its runtime type. This allows you to store various types (like integers, strings, dictionaries, or even complex objects) without defining a specific mapper for each type. However, you need to be aware of some limitations and best practices.

    public class Param
    {
        public int ParamId { get; set; }
        public BsonValue Val { get; set; }  // Change from object to BsonValue
    }
    

    Using BsonValue or object for Dynamic Data OR Storing Complex Objects.

    public class Multi
    {
       public int Code1 { get; set; }
       public int Code2 { get; set; }
    }
    

    You can directly assign an instance of Multi to the Val property.

    var param = new Param
    {
        ParamId = 1,
        Val = new Multi { Code1 = 100, Code2 = 200 }  // Storing a complex object
    };
    

    Handling Dictionaries:

    var param = new Param
    {
        ParamId = 2,
        Val = new Dictionary<int, int> { { 1, 100 }, { 2, 200 } }
    };
    

    Deserializing Dynamic Data:

    When retrieving data from MongoDB, you’ll need to cast or handle the deserialized Val property appropriately. If you are using object or BsonValue, you can check the actual type at runtime and handle it accordingly.

    foreach (var machine in Machines)
    {
        foreach (var param in machine.Params)
        {
            if (param.Val is BsonDocument bsonDoc)  // Complex object case
            {
                var multi = BsonSerializer.Deserialize<Multi>(bsonDoc);
                Console.WriteLine($"Multi Object: Code1={multi.Code1}, Code2={multi.Code2}");
            }
            else if (param.Val is Dictionary<int, int> dictionary)  // Dictionary case
            {
                Console.WriteLine("Dictionary detected:");
                foreach (var kvp in dictionary)
                {
                    Console.WriteLine($"{kvp.Key}: {kvp.Value}");
                }
            }
            else if (param.Val is string strVal)  // String case
            {
                Console.WriteLine($"String: {strVal}");
            }
            else
            {
                Console.WriteLine($"Other type: {param.Val.GetType()}");
            }
        }
    }
    

    BsonDiscriminator for Better Type Handling:

    To help MongoDB identify and deserialize complex types, you can use the [BsonDiscriminator] attribute. This allows MongoDB to store the type information, making deserialization easier.

    [BsonDiscriminator("Multi")]
    public class Multi
    {
        public int Code1 { get; set; }
        public int Code2 { get; set; }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search