Mongo DB remove nested object - c#

I want to remove a data in the my document. I bolded the id I want to remove
My Code like that but "This code deletes all KeyResultActions with same Id";
var filter = builder.Eq("Id", ObjectId.Parse(objectiveId))
& builder.Eq("KeyResults.Id", ObjectId.Parse(keyResultId))
& builder.Eq("KeyResults.KeyResultActions.Id", ObjectId.Parse(actionId));
var update = Builders<Objective>.Update.PullFilter("KeyResults.$[].KeyResultActions",
Builders<KeyResultAction>.Filter.Eq(x => x.Id, ObjectId.Parse(actionId)));
My document like that;
{
"_id": "**6311d1612559020ef536cb6f**",
"KeyResults": [
{
"_id": "6311d1612559020ef536cb69",
"Title": "Test KeyResult -1 ",
"Description": "Test KeyResult Desc -1",
"KeyResultActions": [
{
"_id": "630f5d4ebb4428127b11fb8e"
},
{
"_id": "630f5d4ebb4428127b11fb8f"
}
]
},
{
"_id": "**6311d1612559020ef536cb6b**",
"Title": "Test KeyResult -2",
"Description": "Test KeyResult Desc -2",
"KeyResultActions": [
{
"_id": "**630f5d4ebb4428127b11fb8e**"
},
{
"_id": "630f5d4ebb4428127b11fb8f"
}
]
}
]
}

You shouldn't use the $[] all positional operator which will remove the first item from the KeyResultActions array for all items in the KeyResults.
Instead, replace it with $ positional operator which selects the first matched element in the array.
MongoDB query
db.collection.update({
"_id": "6311d1612559020ef536cb6f",
"KeyResults._id": "6311d1612559020ef536cb6b",
"KeyResults.KeyResultActions._id": "630f5d4ebb4428127b11fb8e"
},
{
$pull: {
"KeyResults.$[].KeyResultActions": {
_id: "630f5d4ebb4428127b11fb8e"
}
}
})
MongoDB .NET Driver syntax
var update = Builders<Objective>.Update.PullFilter("KeyResults.$.KeyResultActions",
Builders<KeyResultAction>.Filter.Eq(x => x.Id, ObjectId.Parse(actionId)));
Demo

Related

Filtering or removing array objects from the DTO class object based on an another list in .net

I have below code :
[
{
"OrderId": "Order1",
"filterOrder": [ "ABC", "XYZ" ],
"Details": [
{
"id": 1,
"value": 100,
"filterDetails": [ "Apples", "Oranges" ]
},
{
"id": 2,
"value": 200,
"filterDetails": [ "Banana", "Blank" ]
}
]
},
{
"OrderId": "Order2",
"filterOrder": [ "PQR", "Blank" ],
"Details": [
{
"id": 1,
"value": 100,
"filterDetails": [ "Apples", "Peaches" ]
},
{
"id": 2,
"value": 200,
"filterDetails": [ "Banana", "Mango" ]
}
]
}
]
C# code:
string i = GetJsonText();
var lst = JsonConvert.DeserializeObject<List<Root>>(i);
My requirement here is to remove all those objects from the response where the filters are mentioned as "Blank".
So I have used the below code to remove the blanks :
lst.RemoveAll(x => x.filterOrder.Contains("Blank"));
lst.ForEach(fe => {fe.Details.RemoveAll(r=> r.filterDetails.Contains("Blank"));});
This is working perfectly fine. But now the requirement changed to remove objects based on multiple strings and not single string. Means I'll have something like
string removeCriteria ="Blank,Blank1,Blank2"
I convert this into a list of strings like this :
List<String> removeList = removeCriteria.Split(",").ToList();
Now I have use removeList in the above code instead of hardcoded "Blank". What can I try to resolve this?
No sure if I am missing something here but isn't this a fairly simple ForEeach loop?
string removeCriteria ="Blank,Blank1,Blank2"
List<String> removeList = removeCriteria.Split(",").ToList();
ForEach (String toRemove in removeList)
{
lst.RemoveAll(x => x.filterOrder.Contains(toRemove));
lst.ForEach(fe => {fe.Details.RemoveAll(r=>r.filterDetails.Contains(toRemove));});
}
For a little better performance, especialy if each parent has alot of child records, I would suggest to change MattBaran answer a little
string removeCriteria ="Blank,Blank1,Blank2"
List<String> removeList = removeCriteria.Split(",").ToList();
//at first remove all parents with children
foreach (var toRemove in removeList)
{
lst.RemoveAll(x => x.filterOrder.Contains(toRemove));
}
//after this check the children of remaining records
foreach (var toRemove in removeList)
{
lst.ForEach(fe => {fe.Details.RemoveAll (r=>r.filterDetails.Contains (toRemove));});
}

How can I merge JSON properties with names that match some pattern into an array-valued property?

How to merge the object of the same key ignoring array wrapper([])?
Below example rows have properties named "elements[0]", "elements[1]" and "elements[2]".
The properties can be at any level on the JSON structure.
more than one array elements like elements[0],[1],[2],[3] and anotherelement[0],[1],[2],[3]
{
"addresses":[
"some address"
],
"rows":[
{
"elements[0]":{
"distance":{
"text":"227 mi",
"value":365468
},
"duration":{
"text":"3 hours 54 mins",
"value":14064
},
"status":"OK"
},
"elements[1]":{
"distance":{
"text":"94.6 mi",
"value":152193
},
"duration":{
"text":"1 hour 44 mins",
"value":6227
},
"status":"OK"
},
"elements[2]":{
"distance":{
"text":"2,878 mi",
"value":4632197
},
"duration":{
"text":"1 day 18 hours",
"value":151772
},
"status":"OK"
}
}
],
"status":[
"OK"
]
}
Expected is the element [{element0, element1, element2}].
{
"addresses":[
"some address"
],
"rows":[
{
"elements": [{
"distance": {
"text": "227 mi",
"value": 365468
},
"duration": {
"text": "3 hours 54 mins",
"value": 14064
},
"status": "OK"
},
{
"distance": {
"text": "94.6 mi",
"value": 152193
},
"duration": {
"text": "1 hour 44 mins",
"value": 6227
},
"status": "OK"
},
{
"distance": {
"text": "2,878 mi",
"value": 4632197
},
"duration": {
"text": "1 day 18 hours",
"value": 151772
},
"status": "OK"
}]}
],
"status":[
"OK"
]
}
The requirement is on the unknown JSON string so, can't create class/model. The above is just an example any generic code would be more helpful.
Updated ::
Thanks #dbc. It looks promising, the order elements will ascending for sure however other keys are swapped
{
"elements[0]": "Value 0",
"elements[1]": "Value 1",
"anotherelement[0]": "Another value 0",
"anotherelement[1]": "Another value 1",
"status" : "OK",
"lastitem" : "yes"
}
result is as below. Is there a way the order of the items as is. I know in JSON it won't affect but just to want to see if possible
{
"status" : "OK",
"lastitem" : "yes",
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
]
}
expected is
{
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
],
"status" : "OK",
"lastitem" : "yes"
}
To restate your problem, you have an arbitrary JSON hierarchy that contains properties whose names end in numerical indices in brackets, like the following (where the values could be of any type):
{
"elements[0]": "Value 0",
"elements[1]": "Value 1",
"anotherelement[0]": "Another value 0",
"anotherelement[1]": "Another value 1"
}
And you would like to transform them into array-valued properties by stripping off the bracketed indices and grouping and combining all the values with identical stripped property names, like so:
{
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
]
}
This can be done using LINQ to JSON to edit your JSON hierarchy. You will also need to use a regular expression to pick out matching property names and a LINQ group statement to group together items with similar names.
The following extension method does the job:
public static partial class JsonExtensions
{
public static void FixElementArrays(this JToken root)
{
var regex = new Regex("^(.+)\\[[0-9]+\\]$");
if (root is JContainer container)
{
var query =
from o in container.DescendantsAndSelf().OfType<JObject>()
let matches = o.Properties()
.Select(p => (Property : p, Match : regex.Match(p.Name)))
.Where(m => m.Match.Success)
.Select(m => (m.Property, Name : m.Match.Groups[1].Value))
let groups = matches.GroupBy(m => m.Name)
from g in groups
select (Object : o, Name : g.Key, Values : g.Select(m => m.Property.Value).ToList());
foreach (var g in query.ToList())
{
IList<JToken> objAsList = g.Object;
// DescendantsAndSelf() returns items in document order, which ordering is preserved by GroupBy, so index of first item should be first index.
var insertIndex = objAsList.IndexOf(g.Values[0].Parent);
g.Values.ForEach(v => v.RemoveFromLowestPossibleParent());
objAsList.Insert(insertIndex, new JProperty(g.Name, new JArray(g.Values)));
}
}
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var property = node.Parent as JProperty;
var contained = property ?? node;
if (contained.Parent != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (property != null)
property.Value = null;
return node;
}
}
Which you can use as follows:
var rootToken = JToken.Parse(jsonString);
rootToken.FixElementArrays();
var fixedJsonString = rootToken.ToString();
Notes:
You may need to tweak the regular expression based on your actual JSON property names. For instance, it's not clear from your question what to do with a name like "[0][1]".
The code assumes that the "elements[*]" properties are already in correct order, i.e. not
{
"elements[3]": "Value 3",
"elements[1]": "Value 1",
"elements[2]": "Value 2"
}
And as such puts them into the final array in the order they are encountered, rather than trying to order then by the index number inside the "elements[*]" property name.
Demo fiddle here.

Search By Partial word of a sentence in elastic search

I am very new to Elastic Search , I want to search a result based on a partial word of a sentence , like the search string is
"val"
and it should search the result with string value
"value is grater than 100"
but if I am using a query
var searchDescriptor = new SearchDescriptor<ElasticsearchProject>()
searchDescriptor.Query(q =>
q.Wildcard(m => m.OnField(p => p.PNumber).Value(string.Format("*{0}*", searchString)))
);
it will work only for one word string like
"ValueIsGraterThan100"
if I use something like this
var searchDescriptor = new SearchDescriptor<ElasticsearchProject>()
searchDescriptor.Query(q =>
q.QueryString(m => m.OnFields(p => p.PName).Query(searchString))
);
This will work for entire word , like i have to provide search string as
"value"
to search
"value is grater than 100"
only providing val will not work.So how i can fulfill my requirement ?
Your field currently is not_analyzed, You can use edge n-gram analyzer made up of edge ngram filter to token your field before saving the fields on inverted index. You can use the following settings
PUT index_name1323
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"standard",
"lowercase",
"filter_edgengram"
]
}
},
"filter": {
"filter_edgengram": {
"type": "edgeNGram",
"min_gram": 2,
"max_gram": 15
}
}
}
},
"mappings": {
"test_type": {
"properties": {
"props": {
"type": "string",
"analyzer": "autocomplete_analyzer"
}
}
}
}
}
Now you can simply use both query_string or term filter to match both your documents to val
POST index_name1323/_search
{
"query": {
"query_string": {
"default_field": "props",
"query": "val"
}
}
}
Hope this helps

How do I properly format this MongoDB update clause?

I have the following document:
{
"_id": {
"$oid": "55e1f841ff149c2228a5c33d"
},
"Status": "Open",
"Date": "8/30/2015",
"ContestName": "Test Contest",
"SearchableContestName": "test contest",
"ClassName": "Test",
"SearchableClassName": "test",
"Judges": [
{
"Name": "First Last",
"IsHeadJudge": null,
"_id": {
"$oid": "55e20962ff149c1f70d1aab0"
},
"ContestScores": null
},
{
"Name": "Another Name",
"IsHeadJudge": null,
"_id": {
"$oid": "55e20947ff149c1f70d1aaaf"
},
"ContestScores": [
1,
2,
3,
4,
5,
6
]
},
...
There are multiple judges in this list. I want to selectively update only the contest scores for the "First Last" name individual. I'm having difficulty figuring out the proper way to build my filters for this. I have:
public async void UpdateContestScores(ContestJudge judgeData, ObjectId contestId)
{
var contests = _db.GetCollection<BsonDocument>("contests");
var builder = Builders<BsonDocument>.Filter;
var updateFilter = builder.Eq("_id", contestId) & builder.Eq("Judges.Name", judgeData.Name);
var update = Builders<BsonDocument>.Update.Set("Judges.ContestScores", judgeData.ContestScores);
await contests.UpdateOneAsync(updateFilter, update);
}
This throws a bulk update error of some form. How do I go about updating this empty contest score field? I'm unsure of the proper filter syntax for such a thing.

mongodb c# how to work with BSON document

I've spent MANY hours looking for the answer...
This is very easy in PHP but I just can't put it together in C#(I'm new to C# and mongo...)
I'm trying to iterate through all levels of a stored document. The document looks like this:
{
"_id": ObjectId("51f90101853bd88971ecdf27"),
"fields": [
{
"ID": ObjectId("51fd09498b080ee40c00514e"),
"NAME": "ID",
"TYPE": "Text"
},
{
"ID": ObjectId("51fd09a68b080ee40c0064db"),
"NAME": "Title",
"TYPE": "Text"
},
{
"ID": ObjectId("51fd09b28b080ee40c004d31"),
"NAME": "Start Date",
"TYPE": "Date"
},
{
"ID": ObjectId("51fd09c28b080ee40c007f2e"),
"NAME": "Long Description",
"TYPE": "Memo"
}
],
"name": "TODB",
"updated": "Wed Jul 31 2013 08:20:17 GMT-0400 (Eastern Daylight Time)"
}
I have no problem accessing the "name" and "updated" but can't figure out how to access the "fields" array.
Code so far :
{
MongoServer mongo = MongoServer.Create();
mongo.Connect();
var db = mongo.GetDatabase("forms");
mongo.RequestStart(db);
var collection = db.GetCollection("forms");
var query = new QueryDocument("name",
"TODB");
mongo.Disconnect();
}
#foreach(BsonDocument item in collection.Find(query))
{
#item.GetElement("name").Value
#item.GetElement("_id").Value
}
Again, I am able to access the name and _id just not any of the sub document values.
Thanks in advance for any assistance!
After I get the reading figured out, I am also going to want to write data....
There are a few ways, but here's one:
// build some test data
BsonArray dataFields = new BsonArray { new BsonDocument {
{ "ID" , ObjectId.GenerateNewId()}, { "NAME", "ID"}, {"TYPE", "Text"} } };
BsonDocument nested = new BsonDocument {
{ "name", "John Doe" },
{ "fields", dataFields },
{ "address", new BsonDocument {
{ "street", "123 Main St." },
{ "city", "Madison" },
{ "state", "WI" },
{ "zip", 53711}
}
}
};
// grab the address from the document,
// subdocs as a BsonDocument
var address = nested["address"].AsBsonDocument;
Console.WriteLine(address["city"].AsString);
// or, jump straight to the value ...
Console.WriteLine(nested["address"]["city"].AsString);
// loop through the fields array
var allFields = nested["fields"].AsBsonArray ;
foreach (var fields in allFields)
{
// grab a few of the fields:
Console.WriteLine("Name: {0}, Type: {1}",
fields["NAME"].AsString, fields["TYPE"].AsString);
}
You can often use the string indexer ["name-of-property"] to walk through the fields and sub document fields. Then, using the AsXYZ properties to cast the field value to a particular type as shown above.

Categories

Resources