I am testing my asp.net core 2.2 web api with Postman. I write the JSON manually like this (httppatch):
{
"query": "{\"name\": \"foo\"}",
"update": [ "{\"$set\":{\"name\":\"foo2\"}}","{\"$set\":{\"path\": \"foo2 path\"}}" ]
}
Now I am thinking how can I build the patch body on the client side.
My question is how can I get the equivalent of this code in json to make it look like the one I write manually?
var query = Builders<T>.Filter.Eq(e => e.name, "foo");
var updates = Builders<T>.Update.Set(e => e.name, "foo2").Set(e => e.Path, "foo2 path");
I guess it's all about serialization, any idea how can I make it?
--Update--
I found this:
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<T>();
var upList = updates.Render(documentSerializer, serializerRegistry);
but it grabs only the last set it combines all sets in one (My bad, thanks to #Simon Mourier to pointing out my mistake !)
Here's the solution:
On the client side
// serializer
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<T>();
// filter and update
var filter = Builders<T>.Filter.Eq(e => e.Level, 2);
var updates = Builders<T>.Update
.Set(e => e.Name, "foo2")
.Set(e => e.Path, "foo2 path")
.Inc(e => e.Level, 1);
// get the string of the filter and the update
var filterString = filter.Render(documentSerializer, serializerRegistry);
var updateString = updates.Render(documentSerializer, serializerRegistry);
// instantiate patch object with properties to json
Patch patch = new Patch()
{
Query = filterString.ToJson(),
Update = updateString.ToJson()
};
// patch object to json
var patchJson = patch.ToJson();
On the server side
[HttpPatch]
public async Task<IActionResult> PatchOne([FromBody]Patch patch)
{
// don't need to ModelState.isValid, it's done on binding
try
{
var update = BsonDocument.Parse(patch.Update);
var filter = BsonDocument.Parse(patch.Query);
var result = await _serviceBase.UpdateOneAsync(filter, update);
...
}
catch (System.Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message.ToJson());
}
}
Global Modals (my solution structure)
public class Patch
{
[Required]
public string Query { get; set; }
[Required]
public string Update { get; set; }
}
Thanks for your help !!
I Find a way to see the query
you need make the query and save it in var
var query = Builders<T>.Filter.Eq(e => e.name, "foo");
var makeQuery = _collection.Find(query).ToString();
makeQuery have the value of Filter.Eq
Related
I am getting MongoDb.Bson.SerializationException: 'Element name '_t' is not valid.
I read all posts online that appear to be similar issue at first, however, in those posts, element name is specified, here, i am only getting '_t' even by trying different class objects.
var database = AppConfig.client.GetDatabase("test");
var collection = database.GetCollection<BsonDocument>("testcollection");
var filter = Builders<Student>.Filter.Eq(g => g.Name, "oldName");
var update = Builders<Student>.Update.Set(g => g.Name, "NewName");
var updateResult = await collection.UpdateOneAsync(filter.ToBsonDocument(), update.ToBsonDocument());
Also, for all examples i have seen online for UpdateOneAsync function, filter and update below do NOT need to be BSON documents, however, my code won't compile unless I do .ToBSONDocument() as above.
var updateResult = await collection.UpdateOneAsync(filter, update);
My class is minimal:
public class Student
{
[BsonElement("Name")]
public string Name { get; set; }
[BsonElement("Age")]
public int Age { get; set; }
}
Can someone please help figure out what is wrong with above?
Update: How to use render for Update.Set
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<Student>();
var filter = Builders<Student>.Filter.Eq(g=> g.Name, "NewName").Render(serializer, registry);
//I think update syntax is not correct.
var update = Builders<Student>.Update.Set(g => g.Name, "Changed").Render(serializer, registry);
//update is throwing error:cannot convert from Mongodb.bson.bsonvalue to mongodb.Driver.updatedefinition<MongoDB.Bson.BsonDocument
var updateResult = await collection.UpdateOneAsync(filter, update);
it's impossible to use ToBsonDocument as you did. The easiest fix is using not typed builders:
var filter = Builders<BsonDocument>.Filter.Eq("name", "oldName");
If you want to use typed builder, you should call Render as below:
var registry = BsonSerializer.SerializerRegistry;
var serializer = registry.GetSerializer<Student>();
var filter = Builders<Student>.Filter.Eq(e=>e.Name, "oldName").Render(serializer, registry);
I am attempting to run a very simple query against my Azure CosmosDb instance. Here is the query:
var query = container.GetItemQueryIterator<string>(new QueryDefinition("SELECT c.id FROM c"));
while (query.HasMoreResults)
{
var feed = query.ReadNextAsync().Result;
var ids = feed.Select(x => x);
foreach(var id in ids)
{
idsInDatabase.Add(id);
}
}
return idsInDatabase.ToArray();
But when it executes, I get an error stating that there is an "Unexpected character" in line 1, position 2, as seen below:
It's like it is trying to parse JSON instead of my SQL query. This example seems to follow the examples I've seen online. What am I missing here?
The result of that query is a JSON that contains an "id" property, not a string, you are not using VALUE.
Have you tried with a Type like:
public class MyResult
{
public string id { get; set; }
}
var query = container.GetItemQueryIterator<MyResult>(new QueryDefinition("SELECT c.id FROM c"));
while (query.HasMoreResults)
{
var feed = query.ReadNextAsync().Result;
foreach(MyResult result in feed)
{
idsInDatabase.Add(result.id);
}
}
return idsInDatabase.ToArray();
I have an object
{
"_id": "testobject",
"A": "First line",
"B": "Second line",
"C": "Third line"
}
I want to send a REST PATCH request to my API to only update one of these properties
{
"_id": "testobject",
"C": "Forth line"
}
This gets parsed into a class
public class SomeObject {
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
I now need to update the existing document in MongoDB but only updating the property C.
I could create an update definition just for this one record
UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>)
Or I could hard code a check on each property to see if it is empty
IList<UpdateDefinition<SomeObject>> updates = new List<UpdateDefinition<SomeObject>>();
if (!string.IsNullOrEmpty(C)) {
updates.Add(UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>));
}
if (!string.IsNullOrEmpty(C)) {
updates.Add(UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>));
}
However, if I have many properties and many sub properties this could get very large very fast. The other issue is that if I set the value of the property to be intentionally empty then it would not update the record at all due to it looking for the field to non-empty.
How can I dynamically do partial updates to MongoDB documents in .NET so that I have a generic PATCH API call that can take any of the parameters the document has and only update the properties specified?
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.
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
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();
An array containing an object will fail:
"array": [{"test": "value"}] will result in {array.test[0] : "value"}, but mongodb expects {array.test.0 : "value"}.
I'm using json.net and i've a json data like that,
[
{
"ID":1098,
"Name":"JC",
"Issues":[
{
"PriorityLevel":"Low",
"State":"Open"
},
{
"PriorityLevel":"Low",
"State":"Open"
}
]
}
]
I just want to get childeren data from Issues via linq. I can reach parent but cannot children. If i reach children data directly i don't need to put more than one for loop.
Thank you.
You can just create a Json Object and extract the properties into an Anonymouse type that you can then query with Linq.
string response = #"[{
""ID"":1098,
""Name"":""JC"",
""Issues"":[
{
""PriorityLevel"":""Low"",
""State"":""Open""
},
{
""PriorityLevel"":""Low"",
""State"":""Open""
}
]}]";
var jsonObject = JObject.Parse(response);
var issues = jsonObject["Issues"].Select(x => new
{
PriorityLevel = (string)x.SelectToken("PriorityLevel"),
State = (string)x.SelectToken("State")
});
You use SelectToken to grab the children of Issues. Now you can query issues for whatever you want.
var lowPriorities = issues.Where(x => x.PriorityLevel == "Low");
Here is a direct link to the json.net page on "Deserializing Using LINQ Example".
Here you go
{
var json = #"[ {
""ID"":1098,
""Name"":""JC"",
""Issues"":[
{
""PriorityLevel"":""Low"",
""State"":""Open""
},
{
""PriorityLevel"":""Low"",
""State"":""Open""
}
]}]";
var a = JArray.Parse(json);
var issues = a.SelectMany (x => x["Issues"]);
var lowPriorities = issues.Where(x => ((string) x["PriorityLevel"]) == "Low");
}
I have a function which returns a JsonResult in the below way.
var attachments = (from a in ar.Attachments
select new { id = a.Id, filename = a.FileName }).ToArray();
var result = new
{
comments = "Some string",
attachments = attachments
};
return this.Json(result);
I need to use this result in another class where I need to access the "comments" and "attachments". Here attachments is a string array and comments is a string. Please let me know how I can do this.
You could create a ViewModel for the result and then just reuse that class. All a ViewModel is, is just a POCO or DTO. The idea is that it gives you a different way to "look" at your data, nothing special really.
So you end up with 3 parts.
The get data method:
public CommentsViewModel GetViewModel()
{
var attachments =
(from a in ar.Attachments
select new { id = a.Id, filename = a.FileName }).ToArray();
var result = new CommentsViewModel
{
comments = "Some string",
attachments = attachments
};
return result;
}
Your controller method:
public JsonResult Get()
{
return this.Json(GetViewModel());
}
And your other method would just call GetViewModel() directly. This would separate this out a bit for you.
Ok, so here is an answer that I believe should fit your needs using the dynamic type...
This is the method you call on the controller...I have put in 'hard coded' sample data as per your requirements for this example...I've removed the 's' from comments just because:
public JsonResult GetJsonData()
{
var result = new
{
comment = "Some string",
attachments = new string[]{"/folder/file-1.jpg", "/folder/file-2.jpg"}
};
return this.Json(result);
}
The code that calls the controller action directly and reads the JsonResult:
dynamic result = GetJsonData().Data;
//var comment will result in a string which equals "Some string" in this example
var comment = result.comment;
//var attachments will result in a string[] which is equal to new string[]{"/folder/file-1.jpg", "/folder/file-2.jpg"}
var attachments = result.attachments;