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

时间:2015-11-30 07:22:07

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

我编写的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"
    }
}

我读到了一个看起来像这样的类:

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字段)。换句话说,最终作为_v内容的部分是我想要作为address字段的值保存到Mongo数据库的内容。

现在,如果我只是从我自己的C#代码中使用Mongo数据库,这可能会很好:如果我要对这个数据结构进行反序列化,我会假设(虽然我还没有验证)Mongo会使用_t_v字段创建正确类型的实例(AddressData),并将它们放在我的Address实例的Person属性中。在这种情况下,一切都会好的。

但我正在与一个 希望在地址数据中看到_t_v值的PHP网络应用共享此数据库,并赢得& #39;不知道如何处理它们。我需要告诉Mongo&#34;请 序列化Address属性的类型。假设它总是成为一个AddressData实例,并且只是序列化其内容而没有任何鉴别器。&#34;

我目前用于将对象持久保存到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&#34;我不关心_t地产上的Address字段,我甚至不想看见。我知道我坚持进入这个领域的对象类型,并且它们总是相同的。&#34;但是我还没有在Mongo文档中发现任何东西告诉我如何做到这一点。有什么建议吗?

3 个答案:

答案 0 :(得分:2)

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

当我发现这个Q&amp; A时,我正在努力解决同样的问题。进一步挖掘后,我发现您可以使用switch删除已接受答案中的BsonDocumentWrapper.Create()语句。这是我找到tip

的链接

这是其他任何人看的例子:

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);
}

答案 1 :(得分:1)

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

答案是显式转换从_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字段的情况下保留,就像我想要的那样

答案 2 :(得分:0)

您可以将对象转换为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;
    }

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