Projection in MongoDB based on Conditions - c#

My document structure in c#:
public class HashTableDocument : Model
{
public int Id { get; set; }
public Dictionary<string, HashSet<int>> items= new Dictionary<string, HashSet<int>>();
}
in Mongo:
{
"_id" : 218,
"items" : {
"1" : [
52711,
201610,
],
"2" : [
246421,
390200
],
"3" : [
105628,
768519
],
"26" : [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
],
"27" : [
26917,
38706,
53862,
111816,
827294,
858348,
870334
]
}
}
I want to be able to pass in any List ('x') of Integers to Mongo. And project on only those key value pairs, if values contains any of the integer in given list ('x').
For example, in above document. if i pass List = { 52711, 105628, 17435, 81421} to Mongo then
It should return
{
"_id" : 218,
"items" : {
"1" : [
52711,
201610,
],
"3" : [
105628,
768519
],
"26" : [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
],
}
}
because each one of those key's value contains at least one element in it's list.

I don't know the C# syntax, but here's how to do it using the aggregation framework. Note that this uses the $objectToArray expression, introduced in version 3.4.4.
> db.test.aggregate([{
$project: {
x: {
$filter: {
input: {$objectToArray: "$items"},
cond: {
$gt: [
{
$size: {
$filter: {
input: "$$this.v",
as: "int",
cond: {$in: ["$$int", [52711, 105628, 17435, 81421]]}
}
}
},
0
]
}
}
}
}
}])
{
"result": [
{
"_id": 218,
"items": [
{
"k": "1",
"v": [
52711,
201610
]
},
{
"k": "3",
"v": [
105628,
768519
]
},
{
"k": "26",
"v": [
17435,
22252,
61389,
65184,
72859,
81421,
931469,
933505,
938377,
959836
]
}
]
}
],
"ok": 1
}
However, it's generally not easy to do such computation when you have a structure like you do. This aggregation cannot use any indexes to restrict its search. Have you considered using the following schema instead?
{
"_id": 218,
"items": [
{k: "1", v: [52711, 201610]},
{k: "2", v: [246421, 390200]},
{k: "3", v: [105628, 768519]},
{k: "26", v: [17435, 22252, 61389, 65184, 72859, 81421, 931469, 933505, 938377, 959836]},
{k: "27", v: [26917, 38706, 53862, 111816, 827294, 858348, 870334]},
]
}
Then your problem becomes much simpler, and you can do the following instead:
db.test.aggregate([
{$match: {"items.v": {$in: [52711, 105628, 17435, 81421]}}},
{
$project: {
items: {
$filter: {
input: "$items",
cond: {
$size: {
$setIntersection:
[[52711, 105628, 17435, 81421], "$$this.v"]
}
}
}
}
}
}
])
And if you created an index on the field "items.v", the initial $match stage could leverage that index to do a more efficient query.

Related

How to deserialize a json array with multiple data types?

I now need to deserialize a JSON that looks like this:
{
"arguments": {
"game": [
"--username",
"--version",
"--assetsDir",
{
"rules": [
{
"action": "allow",
"features": {
"is_demo_user": true
}
}
],
"value": "--demo"
},
{
"rules": [
{
"action": "allow",
"features": {
"has_custom_resolution": true
}
}
],
"value": [
"--width",
"--height"
]
}
]
}
}
As you can see, the array named "game" has both "value" and "object" in it. (But the fact is WORSE than this example, the number of elements is NOT certain)
And the data type of arguments.game[*].value is NOT certain, too.
I used to use classes to describe it, but deserialization failed.
Can't seem to describe an array with multiple element types with a class?
I am using Json.NET. Is there any way to deserialize this "game" array.
Thanks.
Is it a requirement to deserialize to an instance of a class? You could use an ExpandoObject:
using System.Dynamic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
Console.WriteLine("Hello, World!");
string json = #"{
""arguments"": {
""game"": [
""--username"",
""--version"",
""--assetsDir"",
{
""rules"": [
{
""action"": ""allow"",
""features"": {
""is_demo_user"": true
}
}
],
""value"": ""--demo""
},
{
""rules"": [
{
""action"": ""allow"",
""features"": {
""has_custom_resolution"": true
}
}
],
""value"": [
""--width"",
""--height""
]
}
]
}
}";
var expConverter = new ExpandoObjectConverter();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(json, expConverter);
The obj variable will contain the result of the JSON conversion, then you can traverse the dynamic object in code.
For example, to get a list of strings under 'game':
IList<object> list = new List<object>(obj.arguments.game);
foreach (object str in list)
{
if (str as string != null)
{
Console.WriteLine(str as string);
}
}

Project single field in array of subdocument returns more than one

I'm having difficulties putting up a code which returns an element in an array of subdocuments. I am actually trying to flatten a document to a new document which is strongly typed. My document is looking like;
{
"_id" : BinData(3, "7FRf4nbe60ev6XmGKBBW4Q=="),
"status" : NumberInt(1),
"title":"Central station",
"attributes" : [
{
"defId" : BinData(3, "QFDtR03NbkqwuhhG76wS8g=="),
"value" : "388",
"name" : null
},
{
"defId" : BinData(3, "RE3MT3clb0OdLEkkqhpFOg=="),
"value" : "",
"name" : null
},
{
"defId" : BinData(3, "pPgJR50h8kGdDaCcH2o17Q=="),
"value" : "Merkez",
"name" : null
}
]}
What I am trying to achieve is;
{
"title":"Central Station",
"value":"388"
}
What I've done already;
using (_dbContext)
{
var filter = Builders<CustomerModel>.Filter.Eq(q => q.Id, Guid.Parse("30B59585-CBFC-4CD5-A43E-0FDB0AE3167A")) &
Builders<CustomerModel>.Filter.ElemMatch(f => f.Attributes, q => q.DefId == Guid.Parse("47ED5040-CD4D-4A6E-B0BA-1846EFAC12F2"));
var projection = Builders<CustomerModel>.Projection.Include(f => f.Title).Include("attributes.value");
var document = _dbContext.Collection<CustomerModel>().Find(filter).Project(projection).FirstOrDefault();
if (document == null)
return null;
return BsonSerializer.Deserialize<TitleAndValueViewModel>(document);
}
Note: TitleAndCodeViewModel contains title and value properties.
This block of code returns;
{{ "_id" : CSUUID("30b59585-cbfc-4cd5-a43e-0fdb0ae3167a"), "title" : "388 güvenevler", "attributes" : [{ "value" : "388" }, { "value" : "" }, { "value" : "Merkez " }] }}
I am trying to get "value":"388" but instead I am getting another two value properties even tough the ElemMatch filter added for subdocument.
Thank you for your help in advance.
Note: I am looking for answers in C# mongodb driver.
Option 1: ( via aggregation)
db.collection.aggregate([
{
$match: {
_id: 5,
"attributes.defId": 1
}
},
{
"$addFields": {
"attributes": {
"$filter": {
"input": "$attributes",
"as": "a",
"cond": {
$eq: [
"$$a.defId",
1
]
}
}
}
}
},
{
$unwind: "$attributes"
},
{
$project: {
_id: 0,
title: 1,
value: "$attributes.value"
}
}
])
Explained:
Match ( good to add index for the matching fields )
Filter only the attribute you need
Unwind to convert the array to object
Project only the necessary output
Playground
Option 2: ( find/$elemMatch )
db.collection.find({
_id: 5,
attributes: {
"$elemMatch": {
"defId": 1
}
}
},
{
_id: 0,
title: 1,
"attributes": {
"$elemMatch": {
"defId": 1
}
}
})
Explained:
Match the element via _id and elemMatch the attribute
Project the necessary elements. ( Note here elemMatch also need to be used to filter the exact match attribute )
( Note this version will not identify if there is second attribute with same attribute.defId , also projection of attribute will be array with single element if found that need to be considered from the app side )
Playground 2
by specifying defId
db.collection.aggregate(
[{
$project: {
title: '$title',
attributes: {
$filter: {
input: '$attributes',
as: 'element',
cond: { $eq: ['$$element.defId', BinData(3, 'QFDtR03NbkqwuhhG76wS8g==')] }
}
}
}
}, {
$project: {
_id: 0,
title: '$title',
value: { $first: '$attributes.value' }
}
}])
result:
{
"title": "Central station",
"value": "388"
}

Deserialize json array in Xamarin [duplicate]

This question already has answers here:
How to auto-generate a C# class file from a JSON string [closed]
(3 answers)
Closed 1 year ago.
I want to Deserialise this json file in my Xamarin android App to only get the coordinates.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "6849033",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
6.562265,
40.36426
],
[
6.5622743,
40.3642745
],
[
6.5622944,
40.3642897
],etc...
Here is my approach with my class
public class Cadastre
{
public List<List<List<List<float>>>> coordinates { get; set; }
public Cadastre()
{
}
}
And finally here my code to Deserialize my json file
string responseFinished = await GetJson();
Cadastre c = JsonConvert.DeserializeObject<Cadastre>(responseFinished);
I tried many solutions but my coordinates are still null.
If anyone has a solution or a lead I would be grateful.
try this
var jsonObject=JObject.Parse(json);
var coordinates = ((JArray)jsonObject["features"][0]["geometry"]["coordinates"][0][0]).Select(c => new { One = c[0], Two = c[1]}).ToList();
result
[
{
"One": 6.562265,
"Two": 40.36426
},
{
"One": 6.5622743,
"Two": 40.3642745
},
{
"One": 6.5622944,
"Two": 40.3642897
}
]
or if you want to deserialize your way
List<List<List<List<float>>>> coordinates = ((JArray)jsonObject["features"][0]["geometry"]["coordinates"]).ToObject<List<List<List<List<float>>>>>();
result
[
[
[
[
6.562265,
40.36426
],
[
6.5622745,
40.364273
],
[
6.5622945,
40.36429
]
]
]
]

How to write the value of one json property in one line?

It is necessary to write value of "Coordinates" property in json without hyphenation for the following lines, not using ToString() (without converting the value to a string). The desired result is shown below.
{
"Id": null,
"Style": "1234",
"Geometry": {
"Type": "Polygon",
"Coordinates": [[[47541.470259278358,6846.8710054924586],[47540.359922950891,6845.4552435801925],[47541.470259278358,6846.8710054924586]]],
"Properties": [
{
"PointType": "Straight"
},
{
"PointType": "Straight"
},
{
"PointType": "Straight"
}
]
}
}
but not:
{
"Id": null,
"Style": "1234",
"Geometry": {
"Type": "Polygon",
"Coordinates": "[[[47541.470259278358,6846.8710054924586],[47540.359922950891,6845.4552435801925],[47541.470259278358,6846.8710054924586]]]",
"Properties": [
{
"PointType": "Straight"
},
{
"PointType": "Straight"
},
{
"PointType": "Straight"
}
]
}
}
A function that serializes the corresponding class object in json:
JToken ToJson()
{
using (var writer = new JTokenWriter()) {
JsonSerializer serializer = new JsonSerializer();
serializer.Serialize(writer, this);
return writer.Token;
}
}
it seems your second case contains Coordinates property as serialized string.
why should not use
var string = JsonConvert.SerializeObject(YourObject) ?
But you should install https://www.nuget.org/packages/Newtonsoft.Json/ first
You could use string type for properties where you need double quotes and use array or number if you don't need it

C# Filter to get specific Mongo Document

{
"_id" : {
"order" : "0000006"
},
"Catgeory" : [
{
"ID" : "62982698",
"Data" : [
{
"NO" : "62982698",
"History" : [
{
"Status" : null,
}
]
}
]
},
{
"ID" : "63002696",
"Data" : []
}
],
"Info_ID" : [
"6000016405"
]
}
How to write c# mongo filter to get No:62982698 with "Info_ID" :"6000016405". No:62982698 can be part of multiple documents.So want to write filter that filters document with No 62982698 and "Info_ID" :"6000016405"

Categories

Resources