I'm writing C# code that writes to a Mongo database used by an existing Web app (written in PHP), so I need to not change the existing structure of the database. The database structure looks something like this:
{
"_id": ObjectId("5572ee670e86b8ec0ed82c61")
"name": "John Q. Example",
"guid": "12345678-1234-5678-abcd-fedcba654321",
"recordIsDeleted": false,
"address":
{
"line1": "123 Main St.",
"city": "Exampleville"
}
}
I read that in to a class that looks like this:
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.
}
The reading code looks like:
var collection = db.GetCollection<Person>("people");
List<Person> people = collection.Find<Person>(_ => true).ToListAsync().Result;
(Note: I'm still in development. In production, I'm going to switch to ToCursorAsync()
and loop through the data one at a time, so don't worry about the fact that I'm pulling the whole list into memory.)
So far, so good.
However, when I write the data out, this is what it looks like:
{
"_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"
}
}
}
Notice how the address
field looks different. That's not what I want. I want the address data to look just like the address data input (no _t
or _v
fields). In other words, the part that ended up as the contents of _v
is what I wanted to persist to the Mongo database as the value of the address
field.
Now, if I was just consuming the Mongo database from my own C# code, this would probably be fine: if I were to deserialize this data structure, I assume (though I haven't yet verified) that Mongo would use the _t
and _v
fields to create instances of the right type (AddressData
), and put them in the Address
property of my Person
instances. In which case, everything would be fine.
But I'm sharing this database with a PHP web app that is not expecting to see those _t
and _v
values in the address data, and won't know what to do with them. I need to tell Mongo "Please do not serialize the type of the Address
property. Just assume that it's always going to be an AddressData
instance, and just serialize its contents without any discriminators."
The code I'm currently using to persist the objects to Mongo looks like this:
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;
}
Somewhere in there, I need to tell Mongo "I don't care about the _t
field on the Address
property, and I don't even want to see it. I know what type of objects I'm persisting into this field, and they'll always be the same." But I haven't yet found anything in the Mongo documentation to tell me how to do that. Any suggestions?
Thanks @rmunn for this question, it helped me a lot.
I was struggling with this same problem when I found this Q&A. After further digging I found that you can remove the
switch
statement in the accepted answer by usingBsonDocumentWrapper.Create()
. This is a link to where I found the tip.Here's a example for anyone else looking:
You can convert your object to JSON string and from that JSON string you can convert back to BsonArray (if list) or BsonDocument (if object)
Object that you want to update
This will update any type of object list, you just need to pass the object
I figured it out. I was indeed having the problem described at https://groups.google.com/forum/#!topic/mongodb-user/QGctV4Hbipk where Mongo expects a base type but is given a derived type. The base type Mongo was expecting, given my code above, was actually
object
! I discovered thatbuilder.Set()
is actually a generic method,builder.Set<TField>
, which can figure out itsTField
type parameter from the type of its second argument (the field data). Since I was usingprop.GetValue()
, which returnsobject
, Mongo was expecting anobject
instance on myAddress
field (and the other fields that I left out of the question) and therefore putting_t
on all those fields.The answer was to explicitly cast the objects being returned from
prop.GetValue()
, so thatbuilder.Set()
could call the correct generic method (builder.Set<AddressData>()
rather thanbuilder.Set<object>()
) in this case. The following was a bit ugly (I wish there was a way to get a specific generic function overload by reflection at runtime, as I could have converted that wholeswitch
statement to a single reflection-based method call), but it worked:This resulted in the
Address
field, and all the other fields I was having trouble with in my real code, being persisted without any_t
or_v
fields, just like I wanted.