将 C# 类 序列化为 MongoDB 而不在子文档中使用鉴别器

Serializing C# classes to MongoDB without using discriminators in subdocuments

我正在编写 C# 代码来写入现有 Web 应用程序使用的 Mongo 数据库(用 PHP 编写),因此我不需要更改数据库的现有结构。数据库结构如下所示:

{
    "_id": ObjectId("5572ee670e86b8ec0ed82c61")
    "name": "John Q. Example",
    "guid": "12345678-1234-5678-abcd-fedcba654321",
    "recordIsDeleted": false,
    "address":
    {
        "line1": "123 Main St.",
        "city": "Exampleville"
    }
}

我将其读入 class,看起来像这样:

public class Person : MongoMappedBase
{
    public ObjectId Id { get; set; }
    public Guid Guid { get; set; }
    public bool RecordIsDeleted { get; set; }
    public string Name { get; set; }
    public AddressData Address { get; set; }
    // etc.
}

public class AddressData : MongoMappedBase
{
    public string Line1 { get; set; }
    public string City { get; set; }
    // etc.
}

阅读代码如下:

var collection = db.GetCollection<Person>("people");
List<Person> people = collection.Find<Person>(_ => true).ToListAsync().Result;

(注意:我还在开发中。在生产中,我将切换到 ToCursorAsync() 并一次一个地循环数据,所以不要担心我正在将整个列表拉入内存。)

到目前为止,还不错。

然而,当我写出数据时,它是这样的:

{
    "_id": ObjectId("5572ee670e86b8ec0ed82c61")
    "name": "John Q. Example",
    "guid": "12345678-1234-5678-abcd-fedcba654321",
    "recordIsDeleted": false,
    "address":
    {
        "_t": "MyApp.MyNamespace.AddressData, MyApp",
        "_v":
        {
            "line1": "123 Main St.",
            "city": "Exampleville"
        }
    }
}

请注意 address 字段看起来有何不同。 这不是我想要的。 我希望地址数据看起来就像地址数据输入(没有 _t_v 字段)。换句话说,最终作为 _vcontents 的部分是我想作为 [=18] 的值保留到 Mongo 数据库的部分=] 字段.

现在,如果我只是从我自己的 C# 代码中使用 Mongo 数据库,这可能没问题:如果我要反序列化这个数据结构,我假设(虽然我还没有验证) Mongo 将使用 _t_v 字段创建正确类型 (AddressData) 的实例,并将它们放入 Address 属性 我的 Person 个实例。那样的话,就万事大吉了。

但我正在与一个 PHP 网络应用程序共享此数据库,该应用程序 期望看到那些 _t_v 值在地址数据中,并且不知道如何处理它们。我需要告诉 Mongo "Please do not serialize the type of the Address property. Just assume that it's always going to be an AddressData instance, and just serialize its contents without any discriminators."

我目前用来将对象持久化到 Mongo 的代码如下所示:

public UpdateDefinition<TDocument> BuildUpdate<TDocument>(TDocument doc) {
    var builder = Builders<TDocument>.Update;
    UpdateDefinition<TDocument> update = null;
    foreach (PropertyInfo prop in typeof(TDocument).GetProperties())
    {
        if (prop.PropertyType == typeof(MongoDB.Bson.ObjectId))
            continue; // Mongo doesn't allow changing Mongo IDs
        if (prop.GetValue(doc) == null)
            continue; // If we didn't set a value, don't change existing one
        if (update == null)
            update = builder.Set(prop.Name, prop.GetValue(doc));
        else
            update = update.Set(prop.Name, prop.GetValue(doc));
    }
    return update;
}

public void WritePerson(Person person) {
    var update = BuildUpdate<Person>(person);
    var filter = Builders<Person>.Filter.Eq(
        "guid", person.Guid.ToString()
    );
    var collection = db.GetCollection<Person>("people");
    var updateResult = collection.FindOneAndUpdateAsync(
        filter, update
    ).Result;
}

在那里的某个地方,我需要告诉 Mongo "I don't care about the _t field on the Address property, and I don't even want to see it. I know what type of objects I'm persisting into this field, and they'll always be the same." 但是我还没有在 Mongo 文档中找到任何告诉我如何做的东西。有什么建议吗?

我明白了。我确实遇到了 https://groups.google.com/forum/#!topic/mongodb-user/QGctV4Hbipk 中描述的问题,其中 Mongo 需要一个基类型,但被赋予了一个派生类型。根据我上面的代码,预期的基本类型 Mongo 实际上是 object!我发现 builder.Set() 实际上是一个泛型方法,builder.Set<TField>,它可以从它的第二个参数(字段数据)的类型中找出它的 TField 类型参数。由于我使用的是 prop.GetValue(),其中 returns object,Mongo 期望我的 Address 字段(以及其他字段)上有一个 object 实例我遗漏了问题),因此在所有这些字段上放置 _t

答案是显式转换从 prop.GetValue() 返回的对象,以便 builder.Set() 可以调用正确的泛型方法(builder.Set<AddressData>() 而不是 builder.Set<object>())这个案例。以下有点难看(我希望有一种方法可以在运行时通过反射获得特定的通用函数重载,因为我可以将整个 switch 语句转换为单个基于反射的方法调用),但它工作:

public UpdateDefinition<TDocument> BuildUpdate<TDocument>(TDocument doc) {
    var builder = Builders<TDocument>.Update;
    var updates = new List<UpdateDefinition<TDocument>>();
    foreach (PropertyInfo prop in typeof(TDocument).GetProperties())
    {
        if (prop.PropertyType == typeof(MongoDB.Bson.ObjectId))
            continue; // Mongo doesn't allow changing Mongo IDs
        if (prop.GetValue(doc) == null)
            continue; // If we didn't set a value, don't change existing one
        switch (prop.PropertyType.Name) {
        case "AddressData":
            updates.add(builder.Set(prop.Name, (AddressData)prop.GetValue(doc)));
            break;
        // Etc., etc. Many other type names here
        default:
            updates.add(builder.Set(prop.Name, prop.GetValue(doc)));
            break;
        }
    }
    return builder.Combine(updates);
}

这导致 Address 字段,以及我在真实代码中遇到问题的所有其他字段,在没有任何 _t_v 字段的情况下被持久化,就像我想要。

感谢@rmunn 提出这个问题,对我帮助很大

当我发现这个问答时,我正在为同样的问题而苦苦挣扎。进一步挖掘后,我发现您可以使用 BsonDocumentWrapper.Create() 删除接受的答案中的 switch 语句。这是我找到 tip.

的 link

这里有一个例子供其他人参考:

public UpdateDefinition<TDocument> BuildUpdate<TDocument>(TDocument doc) {
    var builder = Builders<TDocument>.Update;
    var updates = new List<UpdateDefinition<TDocument>>();
    foreach (PropertyInfo prop in typeof(TDocument).GetProperties())
    {
        if (prop.PropertyType == typeof(MongoDB.Bson.ObjectId))
            continue; // Mongo doesn't allow changing Mongo IDs
        if (prop.GetValue(doc) == null)
            continue; // If we didn't set a value, don't change existing one

        updates.add(builder.Set(prop.Name, BsonDocumentWrapper.Create(prop.PropertyType, prop.GetValue(doc))));
    }
    return builder.Combine(updates);
}

您可以将您的对象转换为 JSON 字符串,然后您可以从该 JSON 字符串转换回 BsonArray(如果是列表)或 BsonDocument(如果是对象)

您要更新的对象

public  UpdateDefinition<T> getUpdate(T t)
    {
        PropertyInfo[] props = typeof(T).GetProperties();
        UpdateDefinition<T> update = null;
        foreach (PropertyInfo prop in props)
        {


            if (t.GetType().GetProperty(prop.Name).PropertyType.Name == "List`1")
            {
                update = Builders<T>.Update.Set(prop.Name, BsonSerializer.Deserialize<BsonArray>(JsonConvert.SerializeObject(t.GetType().GetProperty(prop.Name).GetValue(t))));
            }
            else if (t.GetType().GetProperty(prop.Name).PropertyType.Name == "object")
            {
                /* if its object */
                update = Builders<T>.Update.Set(prop.Name, BsonSerializer.Deserialize<BsonDocument>(JsonConvert.SerializeObject(t.GetType().GetProperty(prop.Name).GetValue(t))));
            }
            else
            {
                /*if its primitive data type */
                update = Builders<T>.Update.Set(prop.Name, t.GetType().GetProperty(prop.Name).GetValue(t));
            }
        }
        return update;
    }

这将更新任何类型的对象列表,您只需要传递对象