[英]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.