MongoDB C# 2.4 - Find distinct values of nested array - c#

I have documents that look like below:
{
"_id" : ObjectId("58148f4337b1fc09b8c2de9k"),
"Price" : 69.99,
"Attributes" : [
{
"Name" : "Color",
"Value" : "Grey",
},
{
"Name" : "Gender",
"Value" : "Mens",
}
]
}
I am looking to get a distinct list of Attributes.Name (so if I just had the one document as above, I would get 'Color' and 'Gender' returned).
I was able to easily get what I needed through mongo shell (db.getCollection('myCollection').distinct('Attributes.Name'), but I'm really struggling with the C# driver (version 2.4). Can someone please help me translate the shell command to C#?
I tried something like below (and many variations). I'm new to the Mongo C# driver and am just feeling a bit lost. Any help would be appreciated.
var database = client.GetDatabase("mymongodb");
IMongoCollection<BsonDocument> collection = database.GetCollection<BsonDocument>("mycollection");
var filter = new BsonDocument();
var distinctAttributeNames = collection.Distinct<BsonDocument>("Attributes.Name", filter);
var tryAgain = collection.Distinct<BsonDocument>("{Attributes.Name}", filter);

There you go:
public class Foo
{
public ObjectId Id;
public double Price = 69.99;
public Attribute[] Attributes = {
new Attribute { Name = "Color", Value = "Grey" },
new Attribute { Name = "Gender", Value = "Men" }
};
}
public class Attribute
{
public string Name;
public string Value;
}
public class Program
{
static void Main(string[] args)
{
MongoClient client = new MongoClient();
var collection = client.GetDatabase("test").GetCollection<Foo>("test");
collection.InsertOne(new Foo());
var distinctItems = collection.Distinct(new StringFieldDefinition<Foo, string>("Attributes.Name"), FilterDefinition<Foo>.Empty).ToList();
foreach (var distinctItem in distinctItems)
{
Console.WriteLine(distinctItem);
// prints:
// Color
// Gender
}
Console.ReadLine();
}
}

Related

How to change JSON's value for List (or array) type?

"title" : { "newTitle" : "Test"}
"tags" : { "newTags" : ["Tag1", "Tag2"] }
Newtonsoft.Json.Linq;
var json = JObject.Parse(json: json);
var title; // "Test2" in title
List<string> tags; // "TagA", "TagB", "TagC" in tags
json["title"]["newTitle"] = title; // works well
json["tags"]["newTags"] = tags; // not work
I want the JSON result as below:
"title" : { "newTitle" : "Test2"}
"tags" : { "newTags" : ["TagA", "TagB", "TagC"] }
I want to modify some values of JSON. int or string worked well. However, the List or Array does not work.
Please give me a good opinion.
I used a translator. So the writing may be awkward.
Assume your JSON data has defined object template, you can create a class based on your JSON data. With Newtonsoft.Json, you deserialize your JSON into an object and next update the object's properties value.
Note: When access object's inner properties for example Title.NewTitle and Tags.NewTags, you may need to add some null checking for preventing NullReferenceException.
1st solution: Convert to strongly-typed object
public static void Main()
{
var json = "{\"title\" : { \"newTitle\" : \"Test\"}, \"tags\" : { \"newTags\" : [\"Tag1\", \"Tag2\"] }}";
var inputObj = JsonConvert.DeserializeObject<JsonInput>(json);
inputObj.Title.NewTitle = "Test2";
inputObj.Tags.NewTags = new List<string> {"TagA", "TagB", "TagC"};
Console.WriteLine(JsonConvert.SerializeObject(inputObj));
}
public class JsonInput
{
public Title Title {get;set;}
public Tags Tags {get;set;}
}
public class Title
{
public string NewTitle {get;set;}
}
public class Tags
{
public List<string> NewTags {get;set;}
}
1st solution Code snippets and Output
2nd solution: With dynamic
To update array, you need parse your List<string> to JArray type
public static void Main()
{
var json = "{\"title\" : { \"newTitle\" : \"Test\"}, \"tags\" : { \"newTags\" : [\"Tag1\", \"Tag2\"] }}";
var title = "Test2"; // "Test2" in title
List<string> tags = new List<string> {"TagA", "TagB", "TagC"}; // "TagA", "TagB", "TagC" in tags
dynamic root = JObject.Parse(json);
JObject titleObj = (JObject)root["title"];
titleObj["newTitle"] = title;
JObject tagsObj = (JObject)root["tags"];
tagsObj["newTags"] = JArray.FromObject(tags);
Console.WriteLine(root);
}
2nd solution Code snippets and Output
try this
var jsonObject=JObject.Parse(json);
var newTitle = "Test2";
List<string> newTags = new List<string> { "TagA", "TagB", "TagC"};
jsonObject["title"]["newTitle"]= newTitle;
jsonObject["tags"]["newTags"]= JArray.FromObject(newTags);
result
{
"title": {
"newTitle": "Test2"
},
"tags": {
"newTags": [
"TagA",
"TagB",
"TagC"
]
}
}

How to disable discriminator field in MongoDb (C# driver)

Is there a way to disable completely (for all classes) the discriminator ("_t") fields from being added to the bson documents?
I am referring to: mongo-csharp-driver/polymorphism
Let's say we have a Square and Rectangle that inherit from Shape.
public abstract class Shape
{
public ObjectId Id { get; set; }
}
public sealed class Square : Shape
{
public int Size { get; set; }
}
public sealed class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
}
Like you said if we run the following code.
var client = new MongoClient();
var db = client.GetDatabase("test");
var shapes = db.GetCollection<Shape>("shapes");
await shapes.InsertManyAsync(new Shape[]
{
new Square{Size = 10},
new Rectangle{Height = 5, Width = 4}
});
We'll get the following inserted into MongoDB
db.shapes.find()
{ "_id" : ObjectId("5f4e2affc23dde5a501bdf0b"), "_t" : "Square", "Size" : 10 }
{ "_id" : ObjectId("5f4e2affc23dde5a501bdf0c"), "_t" : "Rectangle", "Width" : 4, "Height" : 5 }
Initially, I thought we'd be able to set the DiscriminatorIsRequired flag on the BsonClassMap and wrap that in a convention, however, from trying this it seems to fail due to the following bit of logic in the MongoDB C# Driver.
private bool ShouldSerializeDiscriminator(Type nominalType)
{
return (nominalType != _classMap.ClassType || _classMap.DiscriminatorIsRequired || _classMap.HasRootClass) && !_classMap.IsAnonymous;
}
https://github.com/mongodb/mongo-csharp-driver/blob/9e567e23615c8bb5c7ac1489427c2d15b2124522/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs#L722
So because there's no way for us to tell the serializer we don't want to include a discriminator we'll have to give it a convention instead that does nothing.
If we create an IDiscriminatorConvention that pretty much does nothing and returns back null for the discriminator then the driver won't add this to the document.
public class NullDiscriminatorConvention : IDiscriminatorConvention
{
public static NullDiscriminatorConvention Instance { get; }
= new NullDiscriminatorConvention();
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
=> nominalType;
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
=> null;
public string ElementName { get; } = null;
}
This discriminator convention then needs to be registered against each type.
BsonSerializer.RegisterDiscriminatorConvention(typeof(Square), NullDiscriminatorConvention.Instance);
BsonSerializer.RegisterDiscriminatorConvention(typeof(Rectangle), NullDiscriminatorConvention.Instance);
Alternately if we want it on all types you could do a little bit of reflection.
var shapeTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes(),
(domainAssembly, assemblyType) => new {domainAssembly, assemblyType})
.Where(t => #t.assemblyType.IsSubclassOf(typeof(Shape)))
.Select(t => #t.assemblyType).ToArray();
foreach (var shapeType in shapeTypes)
{
BsonSerializer.RegisterDiscriminatorConvention(shapeType, NullDiscriminatorConvention.Instance);
}
Now if we re-run our code.
var shapeTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes(),
(domainAssembly, assemblyType) => new {domainAssembly, assemblyType})
.Where(t => #t.assemblyType.IsSubclassOf(typeof(Shape)))
.Select(t => #t.assemblyType).ToArray();
foreach (var shapeType in shapeTypes)
{
BsonSerializer.RegisterDiscriminatorConvention(shapeType, NullDiscriminatorConvention.Instance);
}
var client = new MongoClient();
var db = client.GetDatabase("test");
var shapes = db.GetCollection<Shape>("shapes");
await shapes.InsertManyAsync(new Shape[]
{
new Square{Size = 10},
new Rectangle{Height = 5, Width = 4}
});
we'll get our expected output.
db.shapes.find()
{ "_id" : ObjectId("5f4e2d63ed12d7c5d3638d36"), "Size" : 10 }
{ "_id" : ObjectId("5f4e2d63ed12d7c5d3638d37"), "Width" : 4, "Height" : 5 }
One option is using Newtonsoft bson serializer (Newtonsoft.Json.Bson) which gives a lot of serialization options.
It isn't efficient since you need to write the bson to a stream and then read it from there with MongoDb reader but it provides lots of customization option.
Example code:
class BsonDocBuilder
{
private readonly MemoryStream _memStream = new MemoryStream();
private readonly JsonSerializerSettings _serializeSettings = new JsonSerializerSettings();
private readonly JsonSerializer _jsonSerializer;
public BsonDocBuilder()
{
_jsonSerializer = JsonSerializer.Create(_serializeSettings);
}
public BsonDocument ToBson<T>(T value)
{
BsonDocument bd;
try
{
using (BsonDataWriter dataWriter = new BsonDataWriter(_memStream))
{
dataWriter.CloseOutput = false;
_jsonSerializer.Serialize(dataWriter, value);
}
bd= BsonSerializer.Deserialize<BsonDocument>(_memStream.ToArray());
}
finally
{
_memStream.SetLength(0);
}
return bd;
}
}

How to use JSON to fill ObservableCollection?

How to fill ObservableCollection using JSON? Now there is only the script itself and the model in the desktop application. I can not understand how to tie it up.
I get it after running the script:
{
"records": [
{
"brand_id": "1",
"brand_name": "Gigabyte"
},
{
"brand_id": "2",
"brand_name": "MSI"
},
{
"brand_id": "3",
"brand_name": "Lenovo"
},
{
"brand_id": "4",
"brand_name": "Dell"
},
{
"brand_id": "5",
"brand_name": "Google"
}
]}
And I have a Model in app:
public class Brands
{
int brand_id;
string brand_name;
public int Brand_id { get => brand_id; set => brand_id = value; }
public string Brand_name { get => brand_name; set => brand_name = value; }
}
And Collection:
public class BrandsCollection
{
private ObservableCollection<Brands> brands;
public ObservableCollection<Brands> Brands { get => brands; set => brands = value; }
}
It is fairly straight forward, especially with packages available to simplify a lot of the work. The Nuget Package System.Net.Http will have the packages you need to create an HttpClient to Get() your JSON from the web. I recommend Newtonsoft.Json to parse JSON into a C# object. And then having the object you just need to set the DataGrid.ItemSource to be an array of objects of any type for it to generate columns.
A simple example:
First you would define a simple object representation of your JSON data.
For instance if you had the following data:
[
{
"Name":"Test",
"Data": ["Item1","Item2"]
},
{
"Name":"Test 2",
"Data": ["Item3","Item4"]
}
]
You would have to create an equivalent C# representation.
Basically this is a List of objects so:
public class OuterObject : List<InnerObject> {}
The inner object is as follows:
public class InnerObject {
public string Name { get; set; }
public List<string> Data { get; set; }
}
Having the objects defined you can do something like:
HttpClient client = new HttpClient { BaseAddress = new Uri("ADDRESS") };
var json = await client.GetAsync("/ENDPOINT");
JsonSerializer serializer = JsonSerializer.CreateDefault();
using (StringReader reader = new StringReader(json))
{
using (JsonTextReader jsonReader = new JsonTextReader(reader))
{
var result = serializer.Deserialize<OuterObject>(jsonReader);
}
}
Then to access the data in your program you can access it like so:
string name = result[0].Name;
Or set it to a DataGrid's ItemSource to have the Data show up magically.
grid1.ItemSource = result;
As long as the result is an array of items it will create a row per item.
You may want to specify which items are shown but that is done by modifying DataGrid.Columns definitions and setting DataGrid.AutogenerateColumns = false
EDIT: With your data and models
//Just a small change to the Collection
public class BrandsCollection {
private ObservableCollection<Brands> _records;
public ObservableCollection<Brands> records { get => _records; set => _records= value; }
}
And to parse the data...
JsonSerializer serializer = JsonSerializer.CreateDefault();
using (StringReader reader = new StringReader(json))
{
using (JsonTextReader jsonReader = new JsonTextReader(reader))
{
var result = serializer.Deserialize<BrandsCollection>(jsonReader);
}
}
You have to remember to either use the same names as the json labels or use Json Attributes. For more info on that you can go to the official Newtonsoft.Json documentation

How to read appsettings.json with array of values

I have the following appSettings.json file:
"SundrySettings": {
"CookieName": "Cookie",
"AccessGroup": "Software Development",
"Terminals" : {
"Raucherplatz" : "tablet1.local",
"Service" : "tablet2.local",
"Technik" : "tablet3.local",
"Technik" : "tablet4.local",
"Container" : "tablet5.local"
}
}
}
That I would like to load into the following structure:
public class Terminal
{
public string Name;
public string Description;
}
public class SundryOptions
{
public string CookieName { get; set; } = "dummy";
public string HRAccessGroup { get; set; } = "dummy";
public List<Terminal> Terminals;
}
that I would try to load using the following commands:
ServiceProvider sp = services.BuildServiceProvider();
SundryOptions sundryOptions = sp.GetService<IOptions<SundryOptions>>().Value;
The problem I have is that using Property Initialisers never sets the Terminals List correctly. I do need a list (and not a Dictionary) as the enties could be double i.e. Technik in my example.
I am assuming that I have some error in the Class -> I would be happy for any pointers.
Do as follows:
var cookieName = Configuration.GetSection("SundrySettings:CookieName").Value;
var accessGroup = Configuration.GetSection("SundrySettings:AccessGroup").Value;
var terminals = Configuration.GetSection("SundrySettings:Terminals").GetChildren();
List<Terminal> terminalList = new List<Terminal>();
foreach (var keyValuePair in terminals)
{
Terminal termial = new Terminal()
{
Name = keyValuePair.Key,
Description = keyValuePair.Value
};
terminalList.Add(termial);
}
SundryOptions sundryOption = new SundryOptions()
{
CookieName = cookieName,
HRAccessGroup = accessGroup,
Terminals = terminalList
};
I have checked with the exact configuration you provided and it works perfectly.
Implement processing of configuration as following somewhere approporiate like this:
var cookieName =
Configuration.GetSection("SundrySettings:CookieName").Value;
var accessGroup = Configuration.GetSection("SundrySettings:AccessGroup").Value;
var terminals = new List<Terminal>()
var terminalSections = this.Configuration.GetSection("Terminals").GetChildren();
foreach (var item in terminalSections)
{
terminals.Add(new Terminal
{
// perform type mapping here
});
}
SundryOptions sundryOption = new SundryOptions()
{
CookieName = cookieName,
HRAccessGroup = accessGroup,
Terminals = terminalList
};
Of course there could be shorter version, but you can start from here.
If Terminals is a list, in your appSettings, it should be an array, not an object.
"Terminals" : [
"Raucherplatz" : "tablet1.local",
"Service" : "tablet2.local",
"Technik" : "tablet3.local",
"Technik" : "tablet4.local",
"Container" : "tablet5.local"
]

Method inside an array in C#

I just got in contact with C# and I was wondering if it's possible to call a method inside an array. I have to say that I'm working with NoSQL database (mongodb).
This is mi code, and I want to call data() method inside that JSON.
static void Main(string[] args)
{
MongoClient client = new MongoClient();
var db = client.GetDatabase("test");
var collection = db.GetCollection<BsonDocument>("collection");
var document = new BsonDocument
{
{ "date", 10/04/2018 },
{ "data", data() }
};
collection.InsertOneAsync(document);
Console.Read();
}
static void data()
{
for (int i = 1; i <= 50; i++)
{
var data = new BsonDocument
{
{ "magnitude"+i, new BsonDocument{
{ "value", 5 }
} }
};
}
}
EDIT: Basically, what I'm trying to create with C# is this json below. I already did it with PHP and now, I'm trying to do it with C#.
{
"_id" : ObjectId("5abb735eb57dce214009035a"),
"date" : 1262300400,
"data" : {
"magnitude1" : {
"value" : 60
},
"magnitude2" : {
"value" : 38
},
"magnitude3" : {
"value" : 200
},
"magnitude4" : {
"value" : 62
},
"magnitude5" : {
"value" : 153
},
"magnitude6" : {
"value" : 176
},
"magnitude7" : {
"value" : 185
},
"magnitude8" : {
"value" : 168
},
.
.
.
You can use methods to gather data but I'm not sure exactly how you're asking it. Related to the code example I'll just give a simple run down, which is basic programming in general, not just C#.
You can write methods that return void or that return a variable of some type (at a minimum).
//Returns void
public void DoSomething()
{
//Do some work
return;
}
//Returns int
public int GetSomething()
{
int result = 100;
return result;
}
When you have methods that return data you can use them as you would a variable; just remember the method will execute every time it's called so it's often best to save the data to a variable. But for your example you can do something like this.
//other code ommitted
var document = new BsonDocument
{
{ "date", 10/04/2018 },
{ "data", getDocuments() }
};
//remaining code omitted
static List<BsonDocument> getDocuments()
{
var documents = new List<BsonDocument>();
for (int i = 1; i <= 50; i++)
{
var document = new BsonDocument
{
{ "magnitude" + i, new BsonDocument { { "value", 5 } } }
};
documents.Add(document);
}
return documents;
}
Now I modified the data() method to return a list of documents and changed the naming to match it but I'm not sure what you wanted to do with the method. That was my best assumption of what you were trying to accomplish by looking at your code so feel free to ignore all of it if it's wrong.
I've could solve it thanks to #Michael .Code below in case helps to anyone.
static void Main(string[] args)
{
MongoClient client = new MongoClient();
var db = client.GetDatabase("test");
var collection = db.GetCollection<BsonDocument>("Collection");
var document = new BsonDocument
{
{ "date", 10/04/2018 },
{ "data", new BsonDocument{ getDocuments() } }
};
collection.InsertOneAsync(document);
Console.Read();
}
static BsonDocument getDocuments()
{
var documents = new BsonDocument();
for (int i = 1; i <= 5; i++)
{
var document = new BsonDocument
{
{ "magnitude" + i, new BsonDocument { { "value", 5 } } }
};
documents.AddRange(document);
}
return documents;
}

Categories

Resources