I have an object
{
\"_id\": \"testobject\",
\"A\": \"First line\",
\"B\": \"Second line\",
\"C\": \"Third line\"
}
I want to send a RE
Not sure anyone is here >= June '20 however I did the following. I'm using NewtonSoft JObject/JArray and I wanted to create a mongo update parser/function that wouldn't know the incoming schema and would build out nested documents as well. Another thing I had to get used to (I'm new to Mongo) was the syntax of the keys in the Bson Update document i.e.
{ "key.full.path.into.nested.document" : "valueToSet" }
So, after trying a few ways to manually/recursively account for the nesting/containing path of incoming JSON doc, I finally found and can just use JToken.Path property perfectly for this.
Anyway, hopefully this is something someone will find useful. It's just an example and makes a few assumptions about the document structure but is pretty useful in its current form. And, like me, I think it might help a few people that are learning Mongo and their C# driver while also using JSON.Net to wrap the incoming REST requests.
public BsonDocument ParseUpdateRequest(JObject req)
{
BsonDocument bson = new BsonDocument();
Parse(req, ref bson);
BsonDocument parsedBson = new BsonDocument();
parsedBson["$set"] = bson;
return parsedBson;
}
private void Parse(JObject req, ref BsonDocument bson)
{
/**
* Could use a parent key/node in each recursion call or just use the JToken path
* string.IsNullOrEmpty(parentNode) ? field.Key : parentNode + "." + field.Key;
**/
string key;
JToken val;
foreach (var field in req)
{
key = field.Value.Path;
val = field.Value;
switch (val.Type)
{
case JTokenType.String:
bson.Add(key, (string)val);
break;
case JTokenType.Integer:
bson.Add(key, (int)val);
break;
case JTokenType.Float:
bson.Add(key, (float)val);
break;
case JTokenType.Date:
DateTime dt = (DateTime)val;
bson.Add(key, dt.ToUniversalTime());
break;
case JTokenType.Array:
BsonArray bsonArray = ParseArray((JArray)val);
bson.Add(key, bsonArray);
break;
case JTokenType.Object:
Parse((JObject)val, ref bson);
break;
}
}
return;
}
private BsonArray ParseArray(JArray source)
{
BsonArray bson = new BsonArray();
foreach (JToken field in source)
{
switch (field.Type)
{
case JTokenType.String:
bson.Add((string)field);
break;
case JTokenType.Date:
DateTime dt = (DateTime)field;
bson.Add(dt.ToUniversalTime());
break;
case JTokenType.Integer:
bson.Add((int)field);
break;
case JTokenType.Float:
bson.Add((float)field);
break;
case JTokenType.Object:
BsonDocument nestedDoc = new BsonDocument();
Parse((JObject)field, ref nestedDoc);
bson.Add(nestedDoc);
break;
}
}
return bson;
}
And here's some simple test code I wrote:
ModelUser user = new ModelUser();
ControllerApp app = new ControllerApp();
ControllerApp.Instance.User = user;
JObject req = new JObject();
req["first"] = "First";
req["last"] = "Last";
req["usertype"] = "parent";
req["pw"] = "q345n3452345n2345";
req["array"] = JArray.Parse("[ '1', '2', '3' ]");
req["dateTest"] = DateTime.UtcNow;
req["profile"] = new JObject();
req["profile"]["name"] = new JObject();
req["profile"]["name"]["first"] = "testUpdateFirst";
BsonDocument bd;
bd = user.ParseUpdateRequest(req);
string s = bd.ToJson();
I suggest that you avoid relying on 1.x legacy API, as it's perfectly supported in 2.x as well, as shown in the sample code below.
var client = new MongoClient();
var database = client.GetDatabase("test");
var collection = database.GetCollection<BsonDocument>("test");
var changesJson = "{ a : 1, b : 2 }";
var changesDocument = BsonDocument.Parse(changesJson);
var filter = Builders<BsonDocument>.Filter.Eq("_id", 1);
UpdateDefinition<BsonDocument> update = null;
foreach (var change in changesDocument)
{
if (update == null)
{
var builder = Builders<BsonDocument>.Update;
update = builder.Set(change.Name, change.Value);
}
else
{
update = update.Set(change.Name, change.Value);
}
}
//following 3 lines are for debugging purposes only
//var registry = BsonSerializer.SerializerRegistry;
//var serializer = registry.GetSerializer<BsonDocument>();
//var rendered = update.Render(serializer, registry).ToJson();
//you can also use the simpler form below if you're OK with bypassing the UpdateDefinitionBuilder (and trust the JSON string to be fully correct)
update = new BsonDocumentUpdateDefinition<BsonDocument>(new BsonDocument("$set", changesDocument));
var result = collection.UpdateOne(filter, update);
Credits go to Robert Stam for providing the code sample.
In ASP.net core 3.1 you can use the JsonPatchDocument and do a replace in mongodb
JsonPatch in ASP.NET Core web API
[HttpPatch]
public IActionResult JsonPatchWithModelState(
[FromBody] JsonPatchDocument<Customer> patchDoc)
{
if (patchDoc != null)
{
var customer = CreateCustomer();
patchDoc.ApplyTo(customer, ModelState);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return new ObjectResult(customer);
}
else
{
return BadRequest(ModelState);
}
}
You can use
IMongoUpdate updateDoc = new UpdateDocument("$set", doc);
collection.Update(Query.EQ("_id",id), updateDoc);
However, you should be careful.
If you first deserialize your document into SomeObject, all of the fields will get their default value (null for strings, 0 for ints etc). And if you use that object for the update, the fields that didn't exist in your json string would be updated to their default value.
If you use
var bsonDoc = BsonSerializer.Deserialize<BsonDocument>(jsonString);
IMongoUpdate updateDoc = new UpdateDocument("$set", bsonDoc);
collection.Update(Query.EQ("_id",id), updateDoc);
your document on the database will be updated only for the fields that are present in your jsonString