4

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

{
    "_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是我想作为字段值保存到 Mongo 数据库中的内容address

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

但我正在与一个 PHP Web 应用程序共享这个数据库,该应用程序希望在地址数据中看到这些_t_v值,也不知道如何处理它们。我需要告诉 Mongo “请不要序列Address属性的类型。假设它总是一个AddressData实例,并且只序列化它的内容而没有任何鉴别器。”

我目前用来将对象持久保存到 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“我不关心属性_t上的字段Address,我什至不想看到它。我知道我在这个字段中坚持什么类型的对象,他们”永远都是一样的。” 但是我还没有在 Mongo 文档中找到任何东西来告诉我如何做到这一点。有什么建议么?

4

3 回答 3

3

我想到了。我确实遇到了https://groups.google.com/forum/#!topic/mongodb-user/QGctV4Hbipk中描述的问题,其中 Mongo 需要一个基本类型,但给出了一个派生类型。鉴于我上面的代码,Mongo 所期望的基本类型实际上是object!我发现这builder.Set()实际上是一个泛型方法,builder.Set<TField>它可以从它的第二个参数(字段数据)的类型中找出它的TField类型参数。由于我使用的是prop.GetValue(), 它返回object,因此 Mongo 期望在我的字段(以及我忽略的其他字段)上有一个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或字段的情况下持续存在。_v

于 2015-11-30T10:41:20.663 回答
3

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

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

这是其他人的示例:

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);
}
于 2017-01-30T13:49:10.863 回答
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;
    }

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

于 2018-03-01T08:37:44.570 回答