我正在编写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()
并一次循环数据,所以不要担心我将整个列表拉入内存的事实。 )
到现在为止还挺好。
但是,当我写出数据时,这就是它的样子:
{
"_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
属性中。在这种情况下,一切都会好的。
但我正在与一个PHP网络应用程序共享这个数据库,这个应用程序不期望在地址数据中看到那些_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文档中找到任何信息来告诉我如何做到这一点。有什么建议?
感谢@rmunn提出这个问题,它给了我很多帮助。
当我发现这个Q&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);
}
我想到了。我确实遇到了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
字段,就像我想要的那样。
您可以将对象转换为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;
}
这将更新任何类型的对象列表,您只需要传递该对象