如何在C#中为MongoDB设计含必填字段的无模式模型?
问题描述
我有一个客户端和WebApi服务,客户端需要将复杂数据存储到WebApi后端的MongoDB中。客户端知晓数据的完整Schema,但WebApi无需了解。
客户端要存储的对象示例:
{ "id": "123abc", "context": "work", "one": 1, "a": "a", "b": { "c": "c" } }
WebApi仅需通过id和context属性进行过滤操作。请问在C#中该如何设计模型?是否可以定义类似带必填字段的Dictionary<string, object?>?
我尝试了以下代码,但无法正常工作:
/// THE FOLLOWING DOES NOT WORK!!!!! [BsonIgnoreExtraElements] public class MyModel : Dictionary<string, object?> { [BsonId] [BsonRepresentation(BsonType.String)] public string id { get; set; } [BsonElement("context")] [JsonProperty(PropertyName = "context")] public string? context { get; set; } /* The properties "one", "a", "b", "b.c" should get saved and loaded. But I do not want to list them explicitly in this model. */ }
是否存在更适配无模式场景的简化模型设计方案?
解决方案
方案1:直接使用BsonDocument
MongoDB官方驱动自带的BsonDocument天生适配无模式场景,它本质是键值对集合,同时支持强类型访问指定字段:
// 读取时获取id和context var filter = Builders<BsonDocument>.Filter.Eq("id", "123abc"); var document = await collection.Find(filter).FirstOrDefaultAsync(); string id = document["id"].AsString; string? context = document["context"].AsString; // 写入时构造BsonDocument var newDoc = new BsonDocument { {"id", "456def"}, {"context", "personal"}, {"one", 2}, {"a", "b"}, {"b", new BsonDocument("c", "d")} }; await collection.InsertOneAsync(newDoc);
这种方式无需定义自定义模型,灵活度拉满,完全契合WebApi仅操作固定字段的需求。
方案2:混合模型(固定属性+动态字段)
如果想要保留id和context的强类型特性,同时支持动态字段,可以用[BsonExtraElements]标记字典来存储未显式定义的字段,这是MongoDB驱动推荐的兼容方案:
[BsonIgnoreExtraElements(false)] // 确保额外字段被保留 public class MyModel { [BsonId] [BsonRepresentation(BsonType.String)] public string Id { get; set; } = string.Empty; [BsonElement("context")] public string? Context { get; set; } // 所有未显式声明的字段都会自动存入这个字典 [BsonExtraElements] public Dictionary<string, object?> ExtraFields { get; set; } = new(); }
使用示例:
// 创建对象 var model = new MyModel { Id = "123abc", Context = "work", ExtraFields = new Dictionary<string, object?> { {"one", 1}, {"a", "a"}, {"b", new Dictionary<string, object?> {{"c", "c"}}} } }; // 条件查询 var filter = Builders<MyModel>.Filter.Eq(m => m.Id, "123abc"); var result = await collection.Find(filter).FirstOrDefaultAsync(); // 访问动态字段 var aValue = result.ExtraFields["a"] as string; var bCValue = ((Dictionary<string, object?>)result.ExtraFields["b"])["c"] as string;
这个方案兼顾了强类型的安全性和无模式的灵活性,避免了继承字典与MongoDB序列化逻辑冲突的问题(这正是你之前代码失效的原因)。
方案3:使用ExpandoObject
如果需要更极致的动态操作,可以用ExpandoObject——它既可以像普通对象一样访问属性,也能当作字典操作:
// 创建动态对象 dynamic model = new ExpandoObject(); model.id = "123abc"; model.context = "work"; model.one = 1; model.a = "a"; model.b = new ExpandoObject(); model.b.c = "c"; // 插入数据 await collection.InsertOneAsync(model as BsonDocument); // 查询并访问字段 var filter = Builders<BsonDocument>.Filter.Eq("id", "123abc"); var doc = await collection.Find(filter).FirstOrDefaultAsync(); dynamic result = doc; string context = result.context; int one = result.one;
这种方式适合完全动态的场景,但缺点是没有编译时类型检查,操作固定字段时需要注意类型转换。
内容的提问来源于stack exchange,提问作者koalabruder
相关产品推荐
相关产品推荐

