You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:33:10