Count subdocuments using MongoDB C# driver - c#

I want to count and sum all posts in Items. When i query my collection with:
GetCollection().Find(p => p.Type == "Test") i receive this:
[
{
"Type": "Test",
"Items": [
{ "Name": "123", "Id":"123" },
{ "Name": "123", "Id":"123" }
]
},
{
"Type": "Test",
"Items": [
{ "Name": "123", "Id":"123" },
{ "Name": "123", "Id":"123" }
]
}
]
But in this case i want to count all posts in items and the result i want to get is: 4. How can i write a query using MongoDB C# driver to get this?

This is how you can do in mongoDB via aggregation:
db.collection.aggregate([
{
$match: {
Type: "Test"
}
},
{
"$addFields": {
"Items": {
$size: "$Items"
}
}
},
{
$group: {
_id: "Sum",
Total: {
$sum: "$Items"
}
}
}
])
Explained:
Match all documents where Type:"Test"
AddFields/$size to count the Items array elements per document.
Group/Sum to count total Items elements.
With small modification you can adapt to C#
Playground

Assume that the result returns the array/list of Entity, you can work with System.Linq by flattening the list and getting the count of Items.
using System.Linq;
List<Entity> records = GetCollection()
.Find(p => p.Type == "Test")
.ToList();
int count = records
.SelectMany(x => x.Items)
.Count();
public class Entity
{
public string Type { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public string Name { get; set; }
public string Id { get; set; }
}

Related

Searching multiple fields using term query in OpenSearch / ElasticSearch .NET clients

I'm having problem to reproduce search result in OpenSearch / ElasticSearch from DSL query to .NET C# code base.
Index was populated from .NET Core application based on this model:
public class Car
{
public string Producer { get; set; }
public string Model { get; set; }
public string Vin { get; set; }
public ColorEnum Color { get; set; }
public TransmissionType Transmission { get; set; }
public CategoryType Category { get; set; }
}
Index mapping for model above looks like this:
"mappings": {
"_doc": {
"properties": {
"transmission": {
"type": "long"
},
"color": {
"type": "long"
},
"producer": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"model": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"vin": {
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"category": {
"type": "long"
}
}
}
Below is working as expected DSL query
{
"query": {
"bool": {
"must": [
{ "term": { "color": 0 }},
{ "term": { "transmission": 0 }},
{ "term": { "producer": "volvo" } }
]}}
}
The C# query that uses OpenSearch or ElasticSearch .NET Client and should produce that same outcome as DSL query is not respecting all required terms.
var documents = await _openSearchClient.SearchAsync<Car>(s => s
.Index(_indexName)
.Query(q => q
.Bool(b => b
.Must(m => m.Term("category", 0))
.Must(m => m.Term("transmission", 0))
.Must(m => m.Term("producer", producer)))));
return documents?.Documents;
The outcome of C# API call contain also documents that have category values set to 1 and transmission set to 1. It looks like only last term with producer name is respected.
Do i made mistake in c# query or there is something how to queries are executed ?
Thanks for all the help.
I found a solution based on input from this post
var documents = await _openSearchClient.SearchAsync<Car>(s => s
.Index(_indexName)
.Query(q => q
.Bool(bq => bq
.Must(mq => mq
.Term("transmission", 0)
&& mq.Term("color",0)
&& mq.Term("producer",producer)))));

Remove itens from JSON string based on a comparison between two JSON strings - C#

Given 2 JSON strings:
[
{
"id":"BA",
"description":"BrandA",
"values":[
{
"id":"CategoryA",
"description":"CategoryA"
},
{
"id":"CategoryB",
"description":"CategoryB"
},
{
"id":"CategoryC",
"description":"CategoryC"
},
{
"id":"CategoryD",
"description":"CategoryD"
},
{
"id":"CategoryE",
"description":"CategoryE"
},
{
"id":"CategoryF",
"description":"CategoryF"
},
{
"id":"CategoryG",
"description":"CategoryG"
},
{
"id":"CategoryH",
"description":"CategoryH"
}
]
},
{
"id":"BB",
"description":"BrandB",
"values":[
{
"id":"CategoryA",
"description":"CategoryA"
},
{
"id":"CategoryB",
"description":"CategoryB"
},
{
"id":"CategoryC",
"description":"CategoryC"
}
]
}
]
AND
[
{
"id":"BA",
"description":"BrandA",
"values":[
{
"id":"CategoryA",
"description":"CategoryA"
},
{
"id":"CategoryC",
"description":"CategoryC"
}
]
},
{
"id":"BB",
"description":"BrandB",
"values":[
{
"id":"CategoryB",
"description":"CategoryB"
}
]
}
]
First one is the original. The second are the values that I want to remove from the original. So basically, if there is a match on brand and category between first and second JSON, regardless of the order of the elements, I want that match to be removed.
The expected result would be someting like this:
[
{
"id":"BA",
"description":"BrandA",
"values":[
{
"id":"CategoryB",
"description":"CategoryB"
},
{
"id":"CategoryD",
"description":"CategoryD"
},
{
"id":"CategoryE",
"description":"CategoryE"
},
{
"id":"CategoryF",
"description":"CategoryF"
},
{
"id":"CategoryG",
"description":"CategoryG"
},
{
"id":"CategoryH",
"description":"CategoryH"
}
]
},
{
"id":"BB",
"description":"BrandB",
"values":[
{
"id":"CategoryA",
"description":"CategoryA"
},
{
"id":"CategoryC",
"description":"CategoryC"
}
]
}
]
Catagory A and C in Brand A were removed as well as Category B in Brand B.
Based in some research, I was using https://github.com/wbish/jsondiffpatch.net, tried to work with it's functions, but so far I didn't manage to achieve the result I want. Also, to solve this by processing JSON direcly is not a must. If there is a simpler solution to achieve that by converting them to lists and use something like LINQ for example, it works for me as well (tried that, but didn't manage to find a way to do this comparison).
Thanks in advance.
If performance does not matter, the JsonSerializer and LINQ can be used:
Model
public class JsonModel
{
public class ValueModel
{
public string Id { get; set; }
public string Description { get; set; }
}
public string Id { get; set; }
public string Description { get; set; }
public IEnumerable<ValueModel> Values { get; set; }
}
Deserialize and LINQ
string json1Str = #"[...]";
string json2Str = #"[...]";
var opt = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var json1 = System.Text.Json.JsonSerializer.Deserialize<List<JsonModel>>(json1Str, opt);
var json2 = System.Text.Json.JsonSerializer.Deserialize<List<JsonModel>>(json2Str, opt);
var result = json1.
Join(json2, j1 => j1.Id, j2 => j2.Id, (j1, j2) => new JsonModel
{
Id = j1.Id,
Description = j1.Description,
Values = j1.Values.Where(j1 => !j2.Values.Select(val => val.Id).Contains(j1.Id))
});
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
}
Result
[
{
"Id": "BA",
"Description": "BrandA",
"Values": [
{
"Id": "CategoryB",
"Description": "CategoryB"
},
{
"Id": "CategoryD",
"Description": "CategoryD"
},
{
"Id": "CategoryE",
"Description": "CategoryE"
},
{
"Id": "CategoryF",
"Description": "CategoryF"
},
{
"Id": "CategoryG",
"Description": "CategoryG"
},
{
"Id": "CategoryH",
"Description": "CategoryH"
}
]
},
{
"Id": "BB",
"Description": "BrandB",
"Values": [
{
"Id": "CategoryA",
"Description": "CategoryA"
},
{
"Id": "CategoryC",
"Description": "CategoryC"
}
]
}
]

C# Lambda Expression for iterating through nested objects

I have below JSON which I have de-serialized into a C# object and would like to extract the values of INFO_CARD_ID, DOCUMENT_NUM and REVISION_NM at each iteration.
{
"result": {
"message": "Successfully performed search",
"columnlist": "INFO_CARD_ID,DOCUMENT_NUM,REVISION_NM,
"recordcount": 2,
"rows": [
{
"columns": [
{
"value": "UJ3P25HO3JBPLJZ2HU",
"key": "INFO_CARD_ID"
},
{
"value": "DOC1",
"key": "DOCUMENT_NUM"
},
{
"value": "05",
"key": "REVISION_NM"
}
]
},
{
"columns": [
{
"value": "JWQ5TCIV4JC2BCQ4C5",
"key": "INFO_CARD_ID"
},
{
"value": "DOC2",
"key": "DOCUMENT_NUM"
},
{
"value": "05",
"key": "REVISION_NM"
}
]
}
]
}
}
Here are the C# classes:
public class Columns
{
public string value { get; set; }
public string key { get; set; }
}
public class Rows
{
public IList<Columns> columns { get; set; }
}
public class Result
{
public string message { get; set; }
public string columnlist { get; set; }
public int recordcount { get; set; }
public IList<Rows> rows { get; set; }
}
public class searchOutputModel
{
public Result result { get; set; }
}
So far, I have been trying to extract the information on a list with below expression but with no luck, as the individual value is getting stored on each iteration and not all the values at each iteration at the same time.
searchResponse = JsonConvert.DeserializeObject<searchOutputModel>(json);
var dynamicList = searchResponse.result.rows.SelectMany(x => x.columns).Where(y => y.key.Contains("INFO_CARD_ID") || y.key.Contains("DOCUMENT_NUM") || y.key.Contains("REVISION_NM")).Select(y => {
dynamic myValue;
if (y.key.Contains("INFO_CARD_ID"))
myValue = new { INFO_CARD_ID = y.value };
else if (y.key.Contains("DOCUMENT_NUM"))
myValue = new { DOCUMENT_NUM = y.value };
else
myValue = new { REVISION_NM = y.value };
return myValue;
}).ToList();
Expected output:
INFO_CARD_ID DOCUMENT_NUM REVISION_NM
---------------------------------------------
UJ3P25HO3JBPLJZ2HU DOC1 05
JWQ5TCIV4JC2BCQ4C5 DOC2 05
I know I am doing wrong with iteration as it creates new list at each select, but I am not sure how can I achieve the solution that I am looking for. Any help is appreciated.
We just need to select one object per row, no need for SelectMany here. Each property can be gotten by using columns.FirstOrDefault.
I would advise you to create a proper class for this rather than using dynamic but it makes little difference to the logic below.
Not sure why you used Contains on the column.key check, I've used ==
var dynamicList = searchResponse.result.rows.Select(r =>
new {
INFO_CARD_ID = r.columns.FirstOrDefault(c => c.key == "INFO_CARD_ID")?.value,
DOCUMENT_NUM = r.columns.FirstOrDefault(c => c.key == "DOCUMENT_NUM")?.value,
REVISION_NM = r.columns.FirstOrDefault(c => c.key == "REVISION_NM")?.value,
}).ToList();
If you convert each Rows object to a Dictionary, you can use the Dictionary to create objects:
var ans = searchResponse.result.rows
.Select(r => r.columns.ToDictionary(c => c.key, c => c.value))
.Select(cd => new {
INFO_CARD_ID = cd["INFO_CARD_ID"],
DOCUMENT_NUM = cd["DOCUMENT_NUM"],
REVISION_NM = cd["REVISION_NM"]
});

C#/Linq - Group by the 2nd item returned in string array whilst keep parent

I have the following data returned from an API and I am trying to use linq to re-shape the data.
Is it actually possible to do this and get the below expected result.
"results": [
{
"Description": "Describe1",
"Cost": 5.00,
"Category": [
"Online",
"Games"
]
},
{
"Description": "Describe2",
"Cost": 4.00,
"Category": [
"Online",
"Games"
]
},
{
"Description": "Describe3",
"Cost": 3.00,
"Category": [
"Online",
"Grocery"
]
},
{
"Description": "Describe4",
"Cost": 3.00,
"Category": [
"Transport",
"Bus"
]
},
{
"Description": "Describe5",
"Cost": 3.00,
"Category": [
"Transport",
"Bus"
]
},
{
"Description": "Describe5",
"Cost": 10.00,
"Category": [
"Transport",
"Train"
]
}
}
The Final output I am trying to achieve from the above:
{ name : "Online",
data: [
{
name: Games,
value: 9.00
},
{
name : Grocery,
value: 3.00
}],
name : "Transport",
data: [
{
name: Bus,
value: 6.00
},
{
name : Train,
value: 10.00
}],
}
Grouping by FirstOrDefault is all easy enough for first level stats, but I can't see where to start for the sub-grouping!
Thanks in advance.
This should work:
class ResultItem {
public string Name { get; set; }
public double Value { get; set; }
}
class ResultGroup {
public string Name { get; set; }
public ResultItem[] Data { get; set; }
}
var results = items
.GroupBy(x => x.Category[0])
.Select(g => new ResultGroup {
Name = g.Key,
Data = g
.GroupBy(x => x.Category[1])
.Select(g2 => new ResultItem { Name = g2.Key, Value = g2.Sum(x => x.Cost) })
.ToArray()
});
Answering your question in two parts,
Part 1
The first part would consists of few steps
Deserialize existing Json to Custom Data Structures
Modify and create new data structures
The above steps could be achieved with Linq as following.
var parsedData = JsonConvert.DeserializeObject<RootObject>(str).Results.GroupBy(x=> x.Category.First())
.Select(x=>
new {
name= x.Key,
 data =  x.GroupBy(c=>c.Category.Last())
.Select(c=> new {value=c.Sum(f=>f.Cost),name=c.Key})
});
Where RootObject is defined as
public class Result
{
    public string Description { get; set; }
    public double Cost { get; set; }
    public List<string> Category { get; set; }
}
public class RootObject
{
[JsonProperty("results")]
    public List<Result> Results { get; set; }
}
Part 2
The second part has to be executed depended how you want the Json to be formated. If you observe the expected result given in OP, that doesn't look like a valid Json. Simplifying it for representational purposes, the Json string looks like following
{
name : "Online",
data: [....],
name : "Transport",
data: [...],
}
As observed, the name and data fields are duplicated within json. If you would like to have result in this particular manner, you need to Serialize the Json recieved in Part 1, and modify it using string manipulation. For example,
var jsonCollection = parsedData.Select(x=> Regex.Replace(JsonConvert.SerializeObject(x,Newtonsoft.Json.Formatting.Indented),#"^{|}$",string.Empty));
var finalResult = $"{{{string.Join(",",jsonCollection)}}}";
Output
{
"name": "Online",
"data": [
{
"value": 9.0,
"name": "Games"
},
{
"value": 3.0,
"name": "Grocery"
}
]
,
"name": "Transport",
"data": [
{
"value": 6.0,
"name": "Bus"
},
{
"value": 10.0,
"name": "Train"
}
]
}
If your desired result needs to be a valid json, then you could ensure it is an array. For example,
[
{
name : "Online",
data: [....]
},
{
name : "Transport",
data: [...],
}
]
Then you could serialize the result you got in Part 1 directly.
var finalResult = JsonConvert.SerializeObject(parsedData, Newtonsoft.Json.Formatting.Indented);
Output
[
{
"name": "Online",
"data": [
{
"value": 9.0,
"name": "Games"
},
{
"value": 3.0,
"name": "Grocery"
}
]
},
{
"name": "Transport",
"data": [
{
"value": 6.0,
"name": "Bus"
},
{
"value": 10.0,
"name": "Train"
}
]
}
]
Demo Code
You can achieve it in this simple way, Live demo here
var parsedJsonObject = JsonConvert.DeserializeObject<List<ObjectName>>(jsonObject);
var normalizedData = parsedJsonObject.SelectMany(pParent => pParent.Category, (pParent, pCategory) =>
new { pParent, pCategory }).Select(ParentAndCategory =>
new
{
Cost = ParentAndCategory.pParent.Cost,
Category = ParentAndCategory.pCategory,
}).ToList();
var aggregatedData = new List<ObjectName2>();
for(int i = 0; i < (normalizedData.Count - 1);)
{
aggregatedData.Add(new ObjectName2{ Cost = normalizedData[i].Cost, Category1 = normalizedData[i].Category, Category2 = normalizedData[i + 1].Category });
i += 2;
}
var result = aggregatedData.GroupBy(p => p.Category1)
.Select(g => new
{
name = g.Key,
data = g.GroupBy(p => p.Category2).Select(g2 =>
new { name = g2.Key, value = g2.Sum(p2 => p2.Cost) })
}).ToList();
foreach(var item in result)
Console.WriteLine(JsonConvert.SerializeObject(item));
Output
{"name":"Online","data":[{"name":"Games","value":9.00},{"name":"Grocery","value":3.00}]}
{"name":"Transport","data":[{"name":"Bus","value":6.00},{"name":"Train","value":10.00}]}

How use linq multi list criteria?

i want order multi list according to the condition by use other api.
Result (i use from other api)
{
"returned_data": {
"data": [
{
"firstName": "FirstNameAA",
"lastName": "LastNameAA",
"product": [
{
"license": "1AS131",
"carType": "478",
"contract": "0112345",
"amounttoCurrent": 3000
}
]
},
{
"firstName": "FirstNameAA",
"lastName": "LastNameAA",
"product": [
{
"license": "2AS345",
"carType": "465",
"contract": "10234521",
"amounttoCurrent": 12000
}
]
},
{
"firstName": "FirstNameBB",
"lastName": "LastNameBB",
"product": [
{
"license": "kdf9034",
"carType": "4234",
"contract": "8995412",
"amounttoCurrent": 1000
}
]
}
]
}
}
But I want to new result, new order each list by "firstName"
{
"returned_data": {
"data": [
{
"firstName": "FirstNameAA",
"lastName": "LastNameAA",
"product": [
{
"license": "1AS131",
"carType": "478",
"contract": "0112345",
"amounttoCurrent": 3000
},
{
"license": "2AS345",
"carType": "465",
"contract": "10234521",
"amounttoCurrent": 12000
}
]
},
{
"firstName": "FirstNameBB",
"lastName": "LastNameBB",
"product": [
{
"license": "kdf9034",
"carType": "4234",
"contract": "8995412",
"amounttoCurrent": 1000
}
]
}
]
}
}
Code c#
var newResult = resReturnListData.returned_data.data.GroupBy(x => x.firstName); >>> not work.
Please help me. thanks in advane.
Assuming your data structures are as follows,
public class Product
{
public string license { get; set; }
public string carType { get; set; }
public string contract { get; set; }
public int amounttoCurrent { get; set; }
}
public class Datum
{
public string firstName { get; set; }
public string lastName { get; set; }
public List<Product> product { get; set; }
}
public class ReturnedData
{
public List<Datum> data { get; set; }
}
public class RootObject
{
public ReturnedData returned_data { get; set; }
}
You can GroupBy to get the "data" result, and then, wrap it around using an anonymous object.
var resReturnListData = JsonConvert.DeserializeObject<RootObject>(jsonString);
var newResult = resReturnListData.returned_data.data
.GroupBy(x => x.firstName)
.Select(x => new Datum
{
firstName = x.Key,
lastName = x.Select(c => c.lastName).FirstOrDefault(),
product = x.SelectMany(c => c.product).ToList()
});
var finalObject = new RootObject
{
returned_data = new ReturnedData
{
data = newResult.ToList()
}
};
var jsonResult = JsonConvert.SerializeObject(finalObject,Newtonsoft.Json.Formatting.Indented);
Output Sample,
{
"returned_data": {
"data": [
{
"firstName": "FirstNameAA",
"lastName": "LastNameAA",
"product": [
{
"license": "1AS131",
"carType": "478",
"contract": "0112345",
"amounttoCurrent": 3000
},
{
"license": "2AS345",
"carType": "465",
"contract": "10234521",
"amounttoCurrent": 12000
}
]
},
{
"firstName": "FirstNameBB",
"lastName": "LastNameBB",
"product": [
{
"license": "kdf9034",
"carType": "4234",
"contract": "8995412",
"amounttoCurrent": 1000
}
]
}
]
}
}
You need to use proper group by and then select only product from group by result,
var newResult = resReturnListData.returned_data.data
.GroupBy(x => x.firstName)
.Select(g => new
{
firstName = g.Key,
lastName = g.Select(x => x.lastName).FirstOrDefault(),
product = g.SelectMany(x => x.product).ToList()
}).ToList();
If you want to group by your data with firstName and lastName then,
var newResult = resReturnListData.returned_data.data
.GroupBy(x => new { x.firstName, x.lastName })
.Select(g => new
{
firstName = g.Key.firstName,
lastName = g.Key.lastName,
product = g.SelectMany(x => x.product).ToList()
}).ToList();
Usage:
string json = "Your json here";
JObject jObject = JObject.Parse(json);
RootObject resReturnListData = jObject.ToObject<RootObject>();
jObject["returned_data"]["data"] = JToken.FromObject(newResult); //<= newResult comes from either one of above linq group by result
string newJson = jObject.ToString();
Console.WriteLine(newJson);
Output: (From Console)

Categories

Resources