【问题标题】:Mongodb collection as dynamicMongodb 集合为动态
【发布时间】:2025-12-26 01:35:07
【问题描述】:

我有一个应用程序,它有两个相似但不同的对象,我想将这些对象存储在同一个集合中。做这个的最好方式是什么?我如何查询这个集合?

今天我的收藏代表:

public IMongoCollection<Post> Posts
{
    get
    {
        return _database.GetCollection<Post>("posts");
    }
}

我有这门课:

public class Post
{
    public string Id { get; set; }
    public string Message { get; set; }
}

public class NewTypePost
{
    public string Id { get; set; }
    public string Image { get; set; }
}

所以,今天我可以使用 Post 类保存和查询。现在我想存储和检索 Post 和 NewTypePost 这两个类。

我尝试将类类型从 Post 更改为动态。但是当我这样做时,我无法查询集合。

【问题讨论】:

  • 拥有两个集合有什么问题?您始终可以通过_database.GetCollection&lt;BsonDocument&gt;("posts") 获取收藏
  • 两个类太相似,用于同一个海豚。如果我使用两个集合,我将需要在两个地方进行两次查询才能找到我想要的。 Mongo 可以存储不同的对象。我在 node.js 上执行此操作,我不知道如何在 C# 驱动程序上执行此操作。
  • 您希望以什么方式执行查询?最后你将不得不要求ImageMessage 对吗?

标签: c# mongodb mongodb-.net-driver


【解决方案1】:

MongoDB .NET 驱动程序在这种情况下提供的可能性很少:

多态性

您可以构建类的层次结构,MongoDB 驱动程序将能够确定从数据库中检索到的对象的类型:

[BsonKnownTypes(typeof(Post), typeof(NewTypePost))]
public abstract class PostBase
{
    [BsonId]
    public string Id { get; set; }
}


public class Post: PostBase
{        
    public string Message { get; set; }
}

public class NewTypePost: PostBase
{
    public string Image { get; set; }
}

MongoDB 驱动程序将在每个文档中创建额外的字段_t 来表示相应的类。

单课

您仍然可以拥有Post 类并使用BsonIgnoreIfNull 属性来避免序列化异常。如果您的数据库中不存在这些属性,MongoDB .NET 驱动程序会将这些属性设置为 null

public class Post
{   
    [BsonId]
    public string Id { get; set; }
    [BsonIgnoreIfNull]     
    public string Message { get; set; } 
    [BsonIgnoreIfNull]
    public string Image { get; set; }
}

BsonDocument

您也可以放弃强类型方法并使用 BsonDocument 类,这是一种动态的类字典结构,代表您的 Mongo 文档

var collection = db.GetCollection<BsonDocument>("posts");

更多详情here

动态

dynamic 指定为ICollection 的通用参数,您应该会得到一个ExpandoObject 列表,该列表将包含您在数据库中的所有值。

var collection = db.GetCollection<dynamic>("posts");
var data = collection.Find(Builders<dynamic>.Filter.Empty).ToList();
var firstMessage = data[0].Message; // dynamically typed code

【讨论】:

  • 我正在寻找类似多态方法的东西。但是我怎样才能以这种方式查询呢?今天我有这样的东西:dbContext.Posts.ReplaceOneAsync(o => o.Id == posts.Id, postToInsert);
  • 如果您查询 Id 等共享字段,您的 ICollection 仍然可以是 PostBase 类型
【解决方案2】:

假设我有下一个连接到一个测试数据库:

var mongoClient = new MongoClient(new MongoClientSettings
{
    Server = new MongoServerAddress("localhost"),
});
var database = mongoClient.GetDatabase("TestDb");

然后我可以这样做:

var col = database.GetCollection<Post>("posts");
var col2 = database.GetCollection<NewTypePost>("posts");

获取IMongoCollection 的两个不同实例,但指向数据库中的同一个集合。此外,我能够以通常的方式保存到每个集合中:

col.InsertOne(new Post { Message = "m1" });
col2.InsertOne(new NewTypePost { Image = "im1" });

然后,我还可以根据特定字段从这些集合中查询:

var p1= col.Find(Builders<Post>.Filter.Eq(x=>x.Message, "m1")).FirstOrDefault();
var p2 =col2.Find(Builders<NewTypePost>.Filter.Eq(x=>x.Image, "im1")).FirstOrDefault();

Console.WriteLine(p1?.Message); // m1
Console.WriteLine(p2?.Image);  // im1

我不知道这是否是您想要的,但它使用相同的集合。顺便说一句,将 Id 属性更改为用[BsonId, BsonRepresentation(BsonType.ObjectId)] 装饰。希望对您有所帮助。

【讨论】:

    【解决方案3】:

    使用 BsonDocument 数据类型。它可以做到所有这些。 BsonDocument和动态来回很方便。

    public class CustomObject{
          public long Id{get;set;}
          public string Name{get;set;}
          public List<(string,object)> CollectionDynamic{get;set;}
    }
    
    // inserted in mongo
        //public class CustomObject_in_Db{
        //          public long Id {get;set;}
        //          public string Name {get;set;}
        //          public string field2 {get;set;}
        //          public string field3 {get;set;}
        //          public string field4 {get;set;}
        //          public string field5 {get;set;}
        //    }
    

    // 一些代码... mapper(config)

    Automapper.Mapper.CreateMap<BsonDocument,CustomObject>()
        .ForMember(dest=>dest.Id, a=>a.MapFrom(s=>s.Id.GetValue(nameof(CustomObject.Id)).AsInt64)
        .ForMember(dest=>dest.Name, a=>a.MapFrom(s=>s.Id.GetValue(nameof(CustomObject.Name)).AsString)
        .ForMember(dest=>dest.CollectionDynamic, a=>a.MapFrom(s=>_getList(s));
    

    // .......

    private List<(string, object)> _getList(BsonDocument source){
        return source.Elements.Where(e=>!typeof(CustomObject).GetProperties().Select(s=>s.Name).Any(a=>a ==e.Name)).Select(e=>e.Name, BsonTryMapper.MapToDotNetValue(e.Value)));
    }
    

    【讨论】: