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);
Related
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();
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"
}
}
this has been bugging me all night.
I have the following MongoDb function that returns the top searches in a collection including there count
db.search.aggregate([
{
$match: {
keywords: { $not: {$size: 0} }
}
},
{ $unwind: "$term" },
{
$group: {
_id: {$toLower: '$term'},
count: { $sum: 1 }
}
},
{
$match: {
count: { $gte: 2 }
}
},
{ $sort : { count : -1} },
{ $limit : 100 }
]);
I am trying to move this to a C# function and I am getting the error :
MongoDB.Driver.MongoCommandException: 'Command aggregate failed: A
pipeline stage specification object must contain exactly one field..'
Here is my C# version, can anyone see what I am missing?
var pipeline = new BsonDocument[] {
new BsonDocument
{
{
"$match",
new BsonDocument {{"keywords", new BsonDocument {{"$not", new BsonDocument {{"$size", 0}}}}}}
}
},
new BsonDocument {{"$unwind", "$term"}},
new BsonDocument
{
{
"$group", new BsonDocument
{
{"_id", new BsonDocument {{"$toLower", "$term"}}},
{
"count", new BsonDocument
{
{"$sum", 1}
}
}
}
}
},
new BsonDocument
{
{
"$match", new BsonDocument
{
{"count", new BsonDocument {{"$gte", 2}}}
}
},
{
"$sort", new BsonDocument
{
{"count", -1}
}
},
{"$limit", 100}
}
};
var result = collection.Aggregate<BsonDocument>(pipeline).ToListAsync();
Console.WriteLine(result);
$match, $sort and $limit should be separate Aggregation Pipeline stages. In your case they have one root BsonDocument, try:
...
new BsonDocument
{
{
"$match", new BsonDocument
{
{"count", new BsonDocument {{"$gte", 2}}}
}
}
},
new BsonDocument()
{
{ "$sort", new BsonDocument
{
{"count", -1}
}
}
},
new BsonDocument()
{
{ "$limit", 100 }
}
I am using mongo aggregation with match/group. Below is my mongo query & similar i am trying to convert to c#. Able to get date part but struggling for $or part for "ev" field. Need to apply below match/or/group/sort on one collection & lookup on other collection.
db.getCollection("customer").aggregate(
[
{
"$match" : {
"eventTs" : {
"$gte" : ISODate("2020-02-27T00:00:00.000-0500"),
"$lte" : ISODate("2020-02-28T00:00:00.000-0500")
},
$or: [
{ "ev": "JournalAdded" },
{ "ev": "JournalProcessed" },
{ "ev": "JournalModified" },
{ "ev": "JournalVoided" },
{ "ev": "JournalApproved" },
{ "ev": "JournalCancelled" }
]
}
},
{
"$sort" : {
"eid" : 1.0,
"eseq" : 1.0
}
},
{
"$group" : {
"_id" : "$eid",
lastSeq: {$last: "$eseq"},
eventNames:{$push: "$ev"}
}
},
{
"$lookup":
{
from: "xyz",
localField: "_id",
foreignField: "_eid",
as: "Replicated"
}
}
]
);
working c# code:
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{ "eventTs", new BsonDocument
{
{ "$gte", startTs },
{ "$lte", endTs }
}
},
{ "ev", "JournalAdded" },
//{ "ev", "JournalProcessed" },
//{ "ev", "JournalModified" },
//{ "ev", "JournalVoided" },
//{ "ev", "JournalApproved" },
//{ "ev", "JournalCancelled" }
}
}
};
var pipeline = new[] { match };
var result = instructionEventsDocs.Aggregate<BsonDocument>(pipeline);
List<BsonDocument> list = result.ToList();
Need c# equivalent of above mongo query. Can anyone help me with this ?
You can also add the BsonArray after the BsonDocument for $or part for "ev" field,look into the answers in this link Adding BSON array to BsonDocument in MongoDB
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{ "eventTs", new BsonDocument
{
{ "$gte", startTs },
{ "$lte", endTs }
}
},
{
"$or", new BsonArray {
new BsonDocument
{
{ "ev", "JournalAdded" }
},
new BsonDocument
{
{ "ev", "JournalProcessed" }
}
}
},
}
}
};
Equivalent mongo query
{{ "$match" : { "eventTs" : { "$gte" : ISODate("2020-02-27T05:00:00Z"), "$lte" : ISODate("2020-02-28T05:00:00Z") }, "$or" : [{ "ev" : "JournalAdded" }, { "ev" : "JournalProcessed" }] } }}
I am webscraping some data and trying to write the scraped data to a json file using c# newtonsoft.Json.
I get stuck when writing the data to the Json file in my controller.
The multidimensional arrays in c# confuse me.
Thanks in advance.
This is an example of the Json file I am trying to create:
[
{
"testmodule1": {
"name": {
"required": true,
"options": [
"option1",
"option2"
]
},
"admin": {
"required": true,
"options": [
"option1",
"option2"
]
},
"path": {
"required": true,
"options": [
"option1",
"option2"
]
}
}
},
{
"testmodule2": {
"name": {
"required": true,
"options": [
"option1",
"option2"
]
},
"server": {
"required": false,
"options": [
]
},
"port": {
"required": true,
"options": [
"option1",
"option2"
]
}
}
}
]
These are my classes:
public class JsonData
{
public Dictionary<string, JsonParameters> modulename { get; set; }
}
public class JsonParameters
{
public JsonParametersData parameter { get; set; }
}
public class JsonParametersData
{
public bool required { get; set; }
public List<string> options { get; set; }
}
This is my controller, here is where I get stuck. the name modulename does not exist in the current context:
public class WebscrapeController : Controller
{
// GET: Webscrape
public ActionResult Index()
{
List<JsonData> data = new List<JsonData>();
data.Add(new JsonData()
{
modulename = new Dictionary<string, JsonParameters>()
{
modulename.Add("testmodule1", new JsonParameters()
{
parameter = new JsonParametersData()
{
required = true,
options = new List<string>()
{
"option1",
"option2"
}
}
})
}
});
string json = JsonConvert.SerializeObject(data.ToArray());
//write string to file
System.IO.File.WriteAllText(
#"C:mypath",
json);
}
}
Note that the property names "testmodule1" and "testmodule2" as well as "name", "admin", "path", "server" are arbitrary; they differ for each array.
Since the property names "testmodule1" and "testmodule2" as well as "name", "admin", "path", "server" and "port" are arbitrary and not known in advance, you need to model your results array as a List<Dictionary<string, Dictionary<string, JsonParametersData>>>. That is because, when serializing a dictionary to JSON using Json.NET, the dictionary keys become JSON property names.
Thus the JSON above can be created as follows:
// Allocate results using collection initializer syntax for the lists and for the dictionaries.
// https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
// https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-initialize-a-dictionary-with-a-collection-initializer
var results = new List<Dictionary<string, Dictionary<string, JsonParametersData>>>()
{
new Dictionary<string, Dictionary<string, JsonParametersData>>()
{
{
"testmodule1",
new Dictionary<string, JsonParametersData>()
{
{
"name",
new JsonParametersData
{
required = true,
options = new List<string>() { "option1", "option2" },
}
},
{
"admin",
new JsonParametersData
{
required = true,
options = new List<string>() { "option1", "option2" },
}
},
{
"path",
new JsonParametersData
{
required = true,
options = new List<string>() { "option1", "option2" },
}
}
}
},
}
};
var moduleName = "testmodule2";
var moduleParameters = new [] { "name", "server", "port" };
// Now add another result, allocating the dictionary with collection initializer syntax also
results.Add(new Dictionary<string, Dictionary<string, JsonParametersData>>()
{
{
moduleName,
// Loop through the parameters and convert them to a dictionary,
// where the parameter name is the key and the corresponding JsonParametersData is the value
moduleParameters
.ToDictionary(n => n,
n => new JsonParametersData
{
required = true,
options = new List<string>() { "option1", "option2" },
})
}
});
var json = JsonConvert.SerializeObject(results, Formatting.Indented);
Notes:
For documentation on how dictionaries are serialized to JSON, see Serialize a Dictionary and Serialization Guide: Dictionaries and Hashtables.
I am initializing the outermost List<T> using collection initializer syntax.
I am also initializing the dictionaries using collection initializer syntax as shown in How to: Initialize a Dictionary with a Collection Initializer (C# Programming Guide).
Given a collection of parameter names and a way to get the JsonParametersData for each one (not shown in the question), the LINQ extension method Enumerable.ToDictionary<TSource, TKey, TElement>() can be used to construct a Dictionary<string, JsonParametersData> from the parameter collection.
Working sample .Net fiddle here.
Here is a different approach using Newtonsoft JObject.
https://dotnetfiddle.net/TdFDQc
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace StackOverflow
{
public class Program
{
public static void Main(string[] args)
{
JArray array = new JArray();
// Module 1
JObject parameter = new JObject();
AddParameter(parameter, "name", true, new[] { "option1", "option2" });
AddParameter(parameter, "admin", true, new[] { "option1", "option2" });
AddParameter(parameter, "path", false, new[] { "option1", "option2", "option3" });
JObject module = new JObject();
module.Add("testmodule1", parameter);
array.Add(module);
// Module 2
parameter = new JObject();
AddParameter(parameter, "name", true, new[] { "option1", "option2" });
AddParameter(parameter, "server", false, Array.Empty<string>());
AddParameter(parameter, "port", true, new[] { "option1", "option2", "option3" });
module = new JObject();
module.Add("testmodule2", parameter);
array.Add(module);
// Display result
var json = array.ToString();
Console.WriteLine(json);
}
static void AddParameter(JObject jObject, string name, bool required, string[] options)
{
JObject parameterProperties = new JObject();
parameterProperties.Add("required", JToken.FromObject(required));
parameterProperties.Add("options", JToken.FromObject(options));
jObject.Add(name, parameterProperties);
}
}
}