MongoDB C# Driver: Nested Lookups - How do I "join" nested relations? - c#

I have 3 MongoDB collections that are related to each other:
Company
Store: a Company can have multiple Stores
Product: a Store can have multiple Products
Company
{
"_id": { "$oid": "1388445c0000000000000001" },
"name": "Company A",
"stores": [
{ "$oid": "1388445c0000000000000011" },
{ "$oid": "1388445c0000000000000012" }
]
}
Store
{
"_id": { "$oid": "1388445c0000000000000011" },
"name": "Store A",
"products": [
{ "$oid": "1388445c0000000000000021" },
{ "$oid": "1388445c0000000000000022" },
{ "$oid": "1388445c0000000000000023" }
]
}
Product
{
"_id": { "$oid": "1388445c0000000000000021" },
"name": "Product A"
}
If I use Lookup to "join" the first two collections, then the ObjectIds of the Stores are replaced with their corresponding objects from the Store collection:
db.GetCollection<BsonDocument>("Company")
.Aggregate()
.Lookup("Store", "stores", "_id", "stores")
.ToList();
{
"_id": { "$oid": "1388445c0000000000000001" },
"name": "Company A",
"stores": [
{
"_id": { "$oid": "1388445c0000000000000011" },
"name": "Store A",
"products": [
{ "$oid": "1388445c0000000000000021" },
{ "$oid": "1388445c0000000000000022" },
{ "$oid": "1388445c0000000000000023" }
]
},
...
]
}
But I'm struggling to "join" the Products on the nested Stores.
First I tried:
db.GetCollection<BsonDocument>("Company")
.Aggregate()
.Lookup("Store", "stores", "_id", "stores")
.Lookup("Product", "products", "_id", "products")
.ToList();
but obviously, it doesn't work as simple as that. Because the field products doesn't exist on Company, nothing happens.
If I try:
db.GetCollection<BsonDocument>("Company")
.Aggregate()
.Lookup("Store", "stores", "_id", "stores")
.Lookup("Product", "stores.products", "_id", "stores.products")
.ToList();
{
"_id": { "$oid": "1388445c0000000000000001" },
"name": "Company A",
"stores": {
"products": [
{
"_id": { "$oid": "1388445c0000000000000021" },
"name": "Product A"
},
...
]
}
}
then the products are "joined", but all other fields of the Store are gone. Furthermore the field stores is not an array anymore, but an object.
How do I correctly setup the aggregate pipeline with the MongoDB C# Driver to get the 3 collections "joined" so that I receive the following result:
{
"_id": { "$oid": "1388445c0000000000000001" },
"name": "Company A",
"stores": [
{
"_id": { "$oid": "1388445c0000000000000011" },
"name": "Store A",
"products": [
{
"_id": { "$oid": "1388445c0000000000000021" },
"name": "Product A"
},
...
]
}
]
}
Side note:
I'm working with BsonDocument and not a concrete C# type.

I think you should achieve with nested $lookup pipeline as below:
db.Company.aggregate([
{
"$lookup": {
"from": "Store",
"let": {
stores: "$stores"
},
"pipeline": [
{
$match: {
$expr: {
$in: [
"$_id",
"$$stores"
]
}
}
},
{
$lookup: {
"from": "Product",
let: {
products: { products: { $ifNull: [ "$products", [] ] } }
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$products"
]
}
}
}
],
as: "products"
}
}
],
"as": "stores"
}
}
])
Sample Mongo Playground
And convert the query to BsonDocument with MongoDB Compass.
var pipeline = new[]
{
new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "Store" },
{ "let",
new BsonDocument("stores", "$stores")
},
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("$expr",
new BsonDocument("$in",
new BsonArray
{
"$_id",
"$$stores"
}
)
)
),
new BsonDocument("$lookup",
new BsonDocument
{
{ "from", "Product" },
{ "let",
new BsonDocument("products",
new BsonDocument("$ifNull",
new BsonArray
{
"$products",
new BsonArray()
}
)
)
},
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("$expr",
new BsonDocument("$in",
new BsonArray
{
"$_id",
"$$products"
}
)
)
)
}
},
{ "as", "products" }
}
)
}
},
{ "as", "stores" }
}
)
};
var result = _db.GetCollection<BsonDocument>("Company")
.Aggregate<BsonDocument>(pipeline)
.ToList();
Result

Thanx to #Yong Shun I have found the correct answer.
You can build the query also with MongoDB C# types as follows:
PipelineStageDefinition<BsonDocument, BsonDocument> stage = PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument, IEnumerable<BsonDocument>, BsonDocument>(
db.GetCollection<BsonDocument>("Store"),
new BsonDocument("stores", new BsonDocument("$ifNull", new BsonArray { "$stores", new BsonArray() })),
new PipelineStagePipelineDefinition<BsonDocument, BsonDocument>(new List<PipelineStageDefinition<BsonDocument, BsonDocument>>
{
PipelineStageDefinitionBuilder.Match(new BsonDocumentFilterDefinition<BsonDocument>(new BsonDocument("$expr", new BsonDocument("$in", new BsonArray { "$_id", "$$stores" })))),
PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument, IEnumerable<BsonDocument>, BsonDocument>(
db.GetCollection<BsonDocument>("Product"),
new BsonDocument("products", new BsonDocument("$ifNull", new BsonArray { "$products", new BsonArray() })),
new PipelineStagePipelineDefinition<BsonDocument, BsonDocument>(new List<PipelineStageDefinition<BsonDocument, BsonDocument>>
{
PipelineStageDefinitionBuilder.Match(new BsonDocumentFilterDefinition<BsonDocument>(new BsonDocument("$expr", new BsonDocument("$in", new BsonArray { "$_id", "$$products" })))),
}),
"products"
)
}),
"stores"
);
List<BsonDocument> result = db.GetCollection<BsonDocument>("Entity").Aggregate().AppendStage(stage).ToList();

Related

C# Mongodb how do I query a list with subset

[
{
"_id": {
"$oid": "61c474fd740cd6a46a7e8166"
},
"GroupIds": [
{
"$oid": "31c482ff6836e438631995ed"
},
{
"$oid": "11c482ff6836e438631995ee"
},
{
"$oid": "61bb96fb4c3d7106f5b9587a"
}
],
"Username": "Test"
},
{
"_id": {
"$oid": "61c474fd740cd6a46a7e8166"
},
"GroupIds": [
{
"$oid": "15c482ff6836e438631995ed"
},
{
"$oid": "61c482ff6836e438631995ee"
},
{
"$oid": "61bb96ee4c3d7106f5b95879"
}
],
"Username": "Test1"
},
{
"_id": {
"$oid": "21c474fd740cd6a46a7e8166"
},
"GroupIds": [
{
"$oid": "61c482ff6836e438631995ed"
},
{
"$oid": "61c482ff6836e438631995ee"
},
{
"$oid": "61bb96ee4c3d7106f5b95879"
}
],
"Username": "Test2"
},
{
"_id": {
"$oid": "31c474fd740cd6a46a7e8166"
},
"GroupIds": [
{
"$oid": "61c482ff6836e438631995ed"
},
{
"$oid": "61c482ff6836e438631995ee"
},
{
"$oid": "61bb96fb4c3d7106f5b9587a"
}
],
"Username": "Test3"
}
]
Hello, I want to make multiple group based searches in mongo db,
I can make a single group based Search
public async Task<List<List<ObjectId>>> UsersByGroupIdV3(List<List<string>> targetGroupses)
{
List<FilterDefinition<User>> query= new List<FilterDefinition<User>>();
foreach (var targetGroups in targetGroupses)
{
if (targetGroups[0] != "allcountry")
{
query.Add(Builders<User>.Filter.Eq(x => x.GroupIds[0], new ObjectId(targetGroups[0])));
}
if (targetGroups[1] != "allCity")
{
query.Add(Builders<User>.Filter.Eq(x => x.GroupIds[1], new ObjectId(targetGroups[1])));
}
if (targetGroups[2] != "allDistrict")
{
query.Add(Builders<User>.Filter.Eq(x => x.GroupIds[2], new ObjectId(targetGroups[2])));
}
}
var match = Builders<User>.Filter.And(query);
var users = await _userRepository.Find(match);
var group = users.Result.Select(i => i.GroupIds);
return group.ToList();
}
There is a list of nested groups in the targetGroupses parameter
Example
[{["31c482ff6836e438631995ed","11c482ff6836e438631995ee","61bb96fb4c3d7106f5b9587a"]},{["15c482ff6836e438631995ed","61c482ff6836e438631995ee","61bb96ee4c3d7106f5b95879"]}]
var match = Builders.Filter.And(query);
If there is 1 list, it works fine, but if there is more than 1, how can I run the query?
I want to bring those in the America, Alaska, College or Germany Hessen Kreis groups
{[{"America", "Alaska", "College"}],[{"Germany", "Hessen", "Kreis"}]}
I want to fetch what's in this group from mongo db with c#

how to make a duplicate json object's property a parent and merge it's other properties as child in c#

We have json format as shown below and want to normalize it as given in expected output.
Input format:
[
{
"country": "Germany",
"name": "2010",
"value": 40632
},
{
"country": "United States",
"name": "2010",
"value": 0
},
{
"country": "United States",
"name": "2000",
"value": 45986
},
{
"country": "United States",
"name": "1990",
"value": 37060
},
{
"country": "France",
"name": "2010",
"value": 36745
},
{
"country": "France",
"name": "2000",
"value": 34774
}
]
Expected output :
[
{
"name": "Germany",
"series": [
{
"name": "2010",
"value": 40632
}
]
},
{
"name": "United States",
"series": [
{
"name": "2010",
"value": 0
},
{
"name": "2000",
"value": 45986
},
{
"name": "1990",
"value": 37060
}
]
},
{
"name": "France",
"series": [
{
"name": "2010",
"value": 36745
},
{
"name": "2000",
"value": 34774
}
]
}
]
try this
var jArr = JArray.Parse(input);
var groupedData = jArr.GroupBy(a => a["country"]).ToList();
var outputArr = new JArray();
foreach (var item in groupedData)
{
JObject obj = new JObject();
obj.Add("name", item.Key);
var arr = new JArray();
obj.Add("series", arr);
foreach (var jObj in item)
{
JObject newObj = new JObject();
newObj.Add("name", jObj["name"]);
newObj.Add("value", jObj["value"]);
arr.Add(newObj);
}
outputArr.Add(obj);
}
var output = outputArr.ToString();

C# Serialize dictionary without "key" and "value" keywords [duplicate]

This question already has answers here:
C# JSON Serialization of Dictionary into {key:value, ...} instead of {key:key, value:value, ...}
(6 answers)
Closed 2 years ago.
I am trying to serialize the following data structure to JSON:
public class TestClass {
public TestClass() {
Translations = new List<Dictionary<string, Dictionary<string, string>>>();
}
[JsonProperty("translations")]
public List<Dictionary<string, Dictionary<string, string>>> Translations { get; set; }
}
The result json string should look like this:
„translations“: [
„xy“: {
„de-DE“: „Kommando1“,
„en-US“: „Command1“,
(…)
},
„ab“: {
„de-DE“: „Kommando2“,
„en-US“: „Command2“,
(…)
}
]
But instead, this is the output currently:
"translations": [
[
{
"Key": "action1",
"Value": [
{
"Key": "de-DE",
"Value": "Aktion 1 durchgeführt"
}
]
}
],
[
{
"Key": "Aktion2",
"Value": [
{
"Key": "cz-CZ",
"Value": "Zahajit vymenu "
},
{
"Key": "de-DE",
"Value": "Aktion2 durchführen"
},
{
"Key": "en-US",
"Value": "Execute action2"
},
{
"Key": "fr-FR",
"Value": "EXECUTER E Actione"
}
]
}
],
[
{
"Key": "Action3",
"Value": [
{
"Key": "cz-CZ",
"Value": "Vytvorit na vycisteni"
},
{
"Key": "de-DE",
"Value": "Aktion3 generieren"
},
{
"Key": "en-US",
"Value": "Action3 creation"
},
{
"Key": "fr-FR",
"Value": "GENERER MISSION"
}
]
}
], (...)
I would like to know how to serialize the dictionaries without the "key" and "value" strings, but with the values directly (like in the example above): So "en-US": "command2" instead of "key": "en-US", "value": "command2". If this is not possible using dictionary, then I would like to know which container to use in order to achieve this format.
I am writing an answer now because I got it working without any necessary work:
I get the following json-output:
[
{
"first": {
"de-De": "test1",
"de-De1": "test2",
"de-De2": "test3"
},
"second": {
"en-US": "test1us",
"en-US1": "test2us",
"en-US2": "test3us"
}
}
]
By simply doing this:
var Translations = new List<Dictionary<string, Dictionary<string, string>>>();
var dic = new Dictionary<string, string>
{
{"de-De","test1" },
{"de-De1","test2" },
{"de-De2","test3" },
};
var dic2 = new Dictionary<string, string>
{
{"en-US","test1us" },
{"en-US1","test2us" },
{"en-US2","test3us" },
};
var maindic = new Dictionary<string, Dictionary<string, string>>();
maindic.Add("first", dic);
maindic.Add("second", dic2);
Translations.Add(maindic);
var output = JsonConvert.SerializeObject(Translations, Formatting.Indented);
EDIT:
As #Selvin mentioned, it seems like you do not need the List<...> since the listed dictionaries already contain the keys you want to use. So I would go with:
var Translations = new Dictionary<string, Dictionary<string, string>>();
var dic = new Dictionary<string, string>
{
{"action1","test1" },
{"action2","test2" },
{"action3","test3" },
};
var dic2 = new Dictionary<string, string>
{
{"action1","test1us" },
{"action2","test2us" },
{"action3","test3us" },
};
Translations.Add("de-DE", dic);
Translations.Add("en-US", dic2);
var output = JsonConvert.SerializeObject(Translations, Formatting.Indented);
which ultimately leads to:
{
"first": {
"de-De": "test1",
"de-De1": "test2",
"de-De2": "test3"
},
"second": {
"en-US": "test1us",
"en-US1": "test2us",
"en-US2": "test3us"
}
}

How to flatten a JArray of JArray?

I have a JArray of JArrays, but I would like to flatten it into a single JArray of JObjects. I have already implemented a foreach loop which iterates through each JArray in my JArray. I need to know how to flatten each sub-JArray into a JObject.
Here is an example:
[
{
"item": [
{
"fieldName": "Name",
"value": "Andy"
},
{
"fieldName": "Phone",
"value": "678-905-9872"
}
]
},
{
"item": [
{
"fieldName": "Name",
"value": "John"
},
{
"fieldName": "Phone",
"value": "688-954-5678"
}
]
},
{
"item": [
{
"fieldName": "Name",
"value": "Ashley"
},
{
"fieldName": "Phone",
"value": "+44 671 542 8945"
}
]
},
{
"item": [
{
"fieldName": "Name",
"value": "Avi"
},
{
"fieldName": "Phone",
"value": "(212)-908-7772"
}
]
}
]
I would like each item to be a single JObject, resulting in the following JArray:
[
{
"Name": "Andy"
"Phone": "678-905-9872"
},
{
"Name": "John"
"Phone": "688-954-5678"
{
"Name": "Ashley"
"Phone": "+44 671 542 8945"
},
{
"Name": "Avi"
"Phone": "(212)-908-7772"
}
]
Thanks!
EDIT
Here is my solution (c#, using Newtonsoft.Json)
public string ParserFunction(string json)
{
string fieldname, fieldvalue;
JArray jsonArray = JArray.Parse(json);
foreach (JObject item in jsonArray)
{
JArray temp = (JArray)item["columns"]; //create new temporary JArray
foreach (JObject jobject in temp)
{
fieldname = jobject["fieldName"].ToString();
fieldvalue = jobject["value"].ToString();
item.Add(fieldname, fieldvalue);
jobject.Remove("fieldName");
jobject.Remove("value");
}
item.Remove("item");
}
json = jsonArray.ToString();
return json;
}
Not sure if this is the most optimal way to do it, I saw an answer below which looks alright as well.
var jArr = new JArray(JArray.Parse(JSON)
.Select(x => new JObject(new JProperty("Name", x["item"][0]["Name"]),
new JProperty("Phone", x["item"][1]["Phone"])
)
)
);
var str = JsonConvert.SerializeObject(jArr, Formatting.Indented);
str would be:
[
{
"Name": "Andy",
"Phone": "(785) 241-6200"
},
{
"Name": "Arthur Song",
"Phone": "(212) 842-5500"
},
{
"Name": "Ashley James",
"Phone": "+44 191 4956203"
},
{
"Name": "Avi Green",
"Phone": "(212) 842-5500"
}
]

Adding BSON array to BsonDocument in MongoDB

How can I add BsonArray to BsonDocument in MongoDB using a C# driver? I want a result something like this
{
author: 'joe',
title : 'Yet another blog post',
text : 'Here is the text...',
tags : [ 'example', 'joe' ],
comments : [ { author: 'jim', comment: 'I disagree' },
{ author: 'nancy', comment: 'Good post' }
]
}
You can create the above document in C# with the following statement:
var document = new BsonDocument {
{ "author", "joe" },
{ "title", "yet another blog post" },
{ "text", "here is the text..." },
{ "tags", new BsonArray { "example", "joe" } },
{ "comments", new BsonArray {
new BsonDocument { { "author", "jim" }, { "comment", "I disagree" } },
new BsonDocument { { "author", "nancy" }, { "comment", "Good post" } }
}}
};
You can test whether you produced the correct result with:
var json = document.ToJson();
you can also add the array after the BsonDocument already exists, like this:
BsonDocument doc = new BsonDocument {
{ "author", "joe" },
{ "title", "yet another blog post" },
{ "text", "here is the text..." }
};
BsonArray array1 = new BsonArray {
"example", "joe"
};
BsonArray array2 = new BsonArray {
new BsonDocument { { "author", "jim" }, { "comment", "I disagree" } },
new BsonDocument { { "author", "nancy" }, { "comment", "Good post" } }
};
doc.Add("tags", array1);
doc.Add("comments", array2);

Categories

Resources