簡體   English   中英

在子文檔中不使用區分符的情況下將C#類序列化到MongoDB

[英]Serializing C# classes to MongoDB without using discriminators in subdocuments

我正在編寫C#代碼,該代碼將寫入現有Web應用程序(用PHP編寫)所使用的Mongo數據庫中,因此無需更改數據庫的現有結構。 數據庫結構如下所示:

{
    "_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()並一次遍歷一個數據,因此不必擔心我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 )的實例,並將其放在我的Person實例的Address屬性中。 在這種情況下,一切都會好起來的。

但是我與一個PHP Web應用程序共享了該數據庫,該Web應用程序希望在地址數據中看到這些_t_v值,並且不知道如何處理它們。 我要告訴蒙戈“請不要序列化的類型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:“我不在乎Address屬性上的_t字段,我什至都不希望看到它。我知道我將在該字段中保留哪種類型的對象,並且他們將永遠一樣。” 但是我還沒有在Mongo文檔中找到任何可以告訴我如何做的東西。 有什么建議么?

感謝@rmunn這個問題,對我有很大幫助。

當我找到此問答時,我一直在同一個問題中掙扎。 進一步挖掘之后,我發現您可以使用BsonDocumentWrapper.Create()刪除已接受答案中的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

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

我想到了。 我確實遇到了https://groups.google.com/forum/#!topic/mongodb-user/QGctV4Hbipk中描述的問題,其中Mongo希望使用基本類型,但要使用派生類型。 鑒於上面的代碼,Mongo期望的基本類型實際上是object 我發現builder.Set()實際上是一個通用方法builder.Set<TField> ,它可以從第二個參數(字段數據)的類型中找出其TField類型參數。 由於我使用的是prop.GetValue() ,它返回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 ,而沒有任何_t_v字段,就像我想要的那樣。

您可以將對象轉換為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;
    }

這將更新任何類型的對象列表,您只需要傳遞對象

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM