Using Linq syntax, how would you exact match an array inside of a document?
This query works great except that it matches documents who have other Children with ages not 3 or 4.
var query = collection.AsQueryable<Parent>().Where(p => p.Children.Any(c => c.Age == 3) && p.Children.Any(c => c.Age == 4));
For example, it should NOT return this document:
{
"_id" : ObjectId("5514c620923a9b55e22f0adf"),
"Name" : "Bob",
"Children" : [
{
"Name" : "Kid1",
"Age" : 5
},
{
"Name" : "Kid2",
"Age" : 4
},
{
"Name" : "Kid3",
"Age" : 3
}
]
}
And should match this document:
{
"_id" : ObjectId("5514c620923a9b55e22f0adf"),
"Name" : "Bob",
"Children" : [
{
"Name" : "Kid2",
"Age" : 4
},
{
"Name" : "Kid3",
"Age" : 3
}
]
}
This is basically a duplicate of this question, but using linq c# syntax.
Matching an array field which contains any combination of the provided array in MongoDB
Bonus points if you can also show it using c# QueryBuilder instead of linq.
You could create another array, which includes a distinct list of the ages. I.e.:
{
ages: [3,4],
children: [
{ name: "A", age: 3 },
{ name: "A", age: 4 },
{ name: "A", age: 3 }
]
}
Now you can use the $all query: http://docs.mongodb.org/manual/reference/operator/query/all/
Combined with the $size query: http://docs.mongodb.org/manual/reference/operator/query/size/
{ ages: { $all: [3,4], $size: 2 } }
Related
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.
I'm trying to remove all elements from a nested array that match a condition, my document looks like:
{
"_id" : ObjectId("5658a636742a2f0bd8bfe3f1"),
"Name" : "Ade",
"Groups" : [
{
"_id" : ObjectId("565a4d79742a2f14f8d42239"),
"Name" : "GrupoA",
"Members" : [
"F",
"B"
]
},
{
"_id" : ObjectId("565a4df2742a2f14f8d4223a"),
"Name" : "GrupoB",
"Members" : [
"A",
"B",
"C"
]
}
]}
and the code i'm using is:
var filter = Builders<ApplicationUser>.Filter.ElemMatch(x => x.Groups, y => y.Members.Contains("B"));
var updateDefinition = Builders<ApplicationUser>.Update.Pull("Groups.$.Members", "B");
collection.UpdateManyAsync(filter, updateDefinition);
but when I execute it, It only removes the "B" from "GrupoA", and I want to remove all "B" that are found in the array.
I have this following query working on mongo shell as expected.
db.getCollection('personnels').update(
{
_id: ObjectId("55f6728b9d73a15807885de8"),
"Devices._id":ObjectId("55fa5f7ac9e7863a3836e331")
},
{
$pull:{ "Devices.$.DeviceCloudFolders": { "CloudFolderId": ObjectId("5615124b06275f072040c4f1")}}
}
);
And here is my document structure:
{
"_id" : ObjectId("55f6728b9d73a15807885de8"),
"FirstName" : "Tolga",
"Devices" : [
{
"_id" : ObjectId("55fa5f7ac9e7863a3836e331"),
"Name" : "tolga-laptop",
"DeviceCloudFolders" : [{
"AuthorityType" : 1,
"CloudFolderId" : ObjectId("55f96db5c9e7863a3836e310"),
"Status" : 1
}],
"Status" : 1
}
],
"Status" : 1
}
I need to use it in C# and couldn't figure out how.
I started with these lines:
var filter = Builders<Personnel>.Filter.And(
Builders<Personnel>.Filter.Eq("_id", ownerPersonnelId),
Builders<Personnel>.Filter.Eq("Devices._id", _id));
var update = Builders<Personnel>.Update.PullFilter("Devices.$.DeviceCloudFolders", /*couldn't figure out what goes here*/))
Personnels.FindOneAndUpdateAsync(filter, update);
I'm not sure, but you can try using this:
var update = Builders<Personnel>.Update.PullFilter(
"Devices.$.DeviceCloudFolders",
Builders<DeviceCloudFolder>.Filter.Eq("CloudFolderId", _cloudFolderId));
I have some document stored in MongoDB (2.4.9) with and array if documents field:
{
"_id" : "some id",
"class" : "somevalue",
...
"externalAlarmDefinition" : [
{
"idx" : 1,
"inputId" : 1
},
{
"idx" : 2,
"inputId" : 2
},
...
{
"idx" : 6,
"inputId" : 7
}
]
}
For some reason, when I query this object I get BsonElement who's value is a BsonArray with one element - that element in turn is another BsonArray which contains the actual BsonDocuments. See image for the structure:
Does this make sense? I expected that the value of the BsonElement would be a BsonArray with the 6 BsonDocuments.
Am I missing something - can someone explain this?
I am using Mongo Driver 1.9.1.221 that I got using nuget
I'm using version 1.5.0.4566 of the official MongoDB C# driver. I'm using Mongo version 2.06.
Here is what my document structure looks like (omitted fields not necessary for this discussion):
{ "Parents" :
[
{
"CreatedOn": ISODate("2012-07-28T15:30:06.623Z"),
"Title": "Simple Title",
"Children": [
{ "StatusId": 1, "Active" : true, SubChild : { "ExpiresOn": ISODate("2012-07-28T15:30:06.623Z")}},
{ "StatusId": 1, "Active" : true, SubChild : { "ExpiresOn": ISODate("2012-08-28T15:30:06.623Z")}}
]
},
{
"CreatedOn": ISODate("2012-07-28T15:30:06.623Z"),
"Title": "Another Simple Title",
"Children": [
{ "StatusId": 1, "Active" : true, SubChild : { "ExpiresOn": ISODate("2012-07-28T15:30:06.623Z")}},
{ "StatusId": 1, "Active" : true, SubChild : { "ExpiresOn": ISODate("2012-08-28T15:30:06.623Z")}}
]
}
]
}
If I wanted to query the Children that have a StatusId equal to one and Active is true I can use ElemMatch.
Query.ElemMatch("Children", Query.And(Query.EQ("StatusId", 1),Query.EQ("Active",true)));
What I cannot get to work is when I need to include the SubChild element in my query.
Query.ElemMatch("Children", Query.And(Query.EQ("StatusId",1), Query.EQ("Active",true),Query.LT("SubChild.ExpiresOn",DateTime.UtcNow)));
The query doesn't return any values when I try to include the SubChild.ExpiresOn field in the query. I have tried different ways to build this query, but keep getting zero documents when I include the SubChild.ExpiredOn field.
What am I missing?
Thanks,
Try this instead
Query.ElemMatch("Children", Query.And(Query.EQ("StatusId",1), Query.EQ("Active",true),Query.LT("SubChild.ExpiresOn",DateTime.UtcNow)));
Wondering why this query magically works? It's the case (StatusId vs StatusID). JavaScript is case sensitive.
You could eliminate this problem by using strongly typed Linq queries, like:
from x in collection.AsQueryable()
where x.Children.Any(child =>
child.StatusId == 1
&& child.Active
&& child.SubChild.ExpiresOn < DateTime.UtcNow)
select x