C# + EntityFramework: Convert multiple group by query to nested JSON - c#

I made the following linq statement
C#
var list = from row in repository.GetAllEntities()
group row by new { row.RegionString, row.SubRegionString, row.CountryString } into g
select new { g.Key.RegionString, g.Key.SubRegionString, g.Key.CountryString, Count = g.Count() };
return Json(list, JsonRequestBehavior.AllowGet);
That returns
[
{
"RegionString":"Americas",
"SubRegionString":"",
"CountryString":"",
"Count":2
},
{
"RegionString":"Americas",
"SubRegionString":"NorthAmerica",
"CountryString":"Canada",
"Count":5
},
{
"RegionString":"Americas",
"SubRegionString":"NorthAmerica",
"CountryString":"US",
"Count":3
},
{
"RegionString":"Americas",
"SubRegionString":"SouthAmerica",
"CountryString":"Chile",
"Count":3
},
{
"RegionString":"EMEA",
"SubRegionString":"AsiaPacific",
"CountryString":"Australia",
"Count":2
},
{
"RegionString":"EMEA",
"SubRegionString":"AsiaPacific",
"CountryString":"Japan",
"Count":1
},
{
"RegionString":"EMEA",
"SubRegionString":"SouthernEurope",
"CountryString":"Turkey",
"Count":1
},
{
"RegionString":"EMEA",
"SubRegionString":"WesternEurope",
"CountryString":"",
"Count":1
}
]
But I am trying to make it into this format
[{
name: "Americas",
children: [
{
name: "NorthAmerica",
children: [{ "name": "Canada", "count": 5 },
{ "name": "US", "count": 3 }]
},
{
name: "SouthAmerica",
children: [{ "name": "Chile", "count": 1 }]
},
],
},
{
name: "EMA",
children: [
{
name: "AsiaPacific",
children: [{ "name": "Australia", "count": 2 },
{ "name": "Japan", "count": 1 }]
},
{
name: "SouthernEurope",
children: [{ "name": "Turkey", "count": 1 }]
},
],
}]
How could I modify the statement to get the result I'm looking for? Non linq answers are acceptable too.
EDIT: Region is the parent of SubRegion, SubRegion is the parent of Country and Count is the unique number of items that belongs to Country

Here is the linq query you want (I've removed the -String postfix for readability):
var list =
from entity in repository.GetAllEntities()
group entity by entity.Region into regions
let childrenOfRegions =
from region in regions
group region by region.SubRegion into subregions
let countriesOfSubRegions =
from subregion in subregions
group subregion by subregion.Country into countries
select new { Name = countries.Key }
select new { Name = subregions.Key, Children = countriesOfSubRegions }
select new { Name = regions.Key, Children = childrenOfRegions };
There is no Count in this query, since I don't really understand what you are counting.
What I'm doing here is grouping the rows into Regions and in the last line you can see the
select new { Name = regions.Key, ... } part where I'm returning the Regions.
To get the children, you need to group the Regions into SubRegions (the same way with Regions). You repeat this all the way down to the Countries and you're done.

Related

C# MongoDB - Filtering nested array data

i am new on MongoDB and i am trying to use it in C# context. Let´s say, i have documents like this:
[
{
"Number": "2140007529",
"Name": "ABC",
"IsInactive": true,
"EntryList": [
{
"Timestamp": "2022-06-01T14:00:00.000+00:00",
"Value": 21564.0
},
{
"Timestamp": "2022-07-01T21:31:00.000+00:00",
"Value": 21568.0
},
{
"Timestamp": "2022-08-02T21:21:00.000+00:00",
"Value": 21581.642
},
{
"Timestamp": "2022-09-02T15:42:00.000+00:00",
"Value": 21593.551
},
{
"Timestamp": "2022-09-26T13:00:00.000+00:00",
"Value": 21603
}
]
},
{
"Number": "2220000784",
"Name": "XYZ",
"IsInactive": false,
"EntryList": [
{
"Timestamp": "2022-09-26T13:00:00.000+00:00",
"Value": 0.0
},
{
"Timestamp": "2022-10-01T08:49:00.000+00:00",
"Value": 5.274
},
{
"Timestamp": "2022-11-01T09:56:00.000+00:00",
"Value": 76.753
},
{
"Timestamp": "2022-12-01T19:43:00.000+00:00",
"Value": 244.877
},
{
"Timestamp": "2023-01-01T11:54:00.000+00:00",
"Value": 528.56
},
{
"Timestamp": "2023-02-01T17:21:00.000+00:00",
"Value": 802.264
}
]
}
]
I want to get the document where the IsInactive flag is false. But for the EntryList there should be returned entries greater than Timestamp 2022-12-31 only.I should look like this:
{
"Number": "2220000784",
"Name": "XYZ",
"IsInactive": false,
"EntryList": [
{
"Timestamp": "2023-01-01T11:54:00.000+00:00",
"Value": 528.56
},
{
"Timestamp": "2023-02-01T17:21:00.000+00:00",
"Value": 802.264
}
]
}
So, here is my question. How can i filter nested arrays in return value with C#. Thanks for help!
I tried to get the result with aggregate function of MongoDB in MongoDB Compass. I got it work with but not in C#.
I think you are looking for a query similar to this one.
So you can try something like this code:
var desiredTimestamp = new DateTime(2022, 12, 31);
var results = collection.AsQueryable()
.Where(x => x.IsInactive == false && x.EntryList.Any(e => e.Timestamp >= desiredTimestamp))
.Select(obj => new
{
Number = obj.Number,
Name = obj.Name,
IsInactive = obj.IsInactive,
EntryList = obj.EntryList
.Where(e => e.Timestamp >= desiredTimestamp)
.ToList()
}).ToList()
Note that I'm assuming your Timestamp is a date type, otherwise you can't compare date and string.

How can I filter Mongodb specific document's subdocument

Consider you have Order and OrderDetails. I'm trying to filter order details for the specific order.
For instance if you have document something like this:
{
"orders": [
{
"id": 1,
"date": "02.04.2020 ...",
"user": {
"name": "xx",
"surname": "yy"
},
"orderDetails": [
{
"id": 1,
"productId": 5,
"quantity": 1,
"state": 3
},
{
"id": 2,
"productId": 3,
"quantity": 4,
"state": 3
},
{
"id": 3,
"productId": 4,
"quantity": 12,
"state": 2
},
{
"id": 4,
"productId": 7,
"quantity": 8,
"state": 2
},
{
"id": 5,
"productId": 12,
"quantity": 9,
"state": 3
}
]
},
{
"id": 2,
"date": "01.04.2020 ...",
"user": {
"name": "xx",
"surname": "yy"
},
"orderDetails": [
{
"id": 6,
"productId": 5,
"quantity": 1,
"state": 3
},
{
"id": 7,
"productId": 3,
"quantity": 4,
"state": 3
},
{
"id": 8,
"productId": 4,
"quantity": 12,
"state": 2
}
]
}
}
What I'm trying to do is first filtering by order and then state of an order detail. I have a code like this but it always brings correct order with all orderDetails. Seems like it doesn't care about equal filter for orderDetails.
Actually it's working but not filtering. Because I only have 3 types of state(enum) and int values are 1,2,3. Query brings nothing if I give 4.
var builder = Builders<Order>.Filter;
var filter = builderk.And(builder.Eq("_id", ObjectId.Parse(elementid)), builder.Eq("orderDetails.state", 3));
var result = _mongoRepository.FindByFilter(filter).ToList();
I also tried AnyEq and something like that filters but didn't work.
I will be very happy if anyone can help me.
Thanks.
You can use aggregation operation for operations such as filter sorting in detail records.
If we continue through the sample, you must first create a filter for your master data.
var builderMaster = Builders<Order>.Filter;
var filterMaster = builderMaster.Eq("_id", ObjectId.Parse(elementid));
Then you need to create a different filter for the details.
Important: You must use the BsonDocument type when creating detail filters. Because you cannot give a specific type when you filter the details.
var builderDetail = Builders<BsonDocument>.Filter;
var filterDetail = builderDetail.Eq("orderDetails.state", 3);
Then you can start typing the query.
var list = _mongoRepository.Aggregate()
.Match(filterMaster)
.Unwind("orderDetails")// Name the details.
.Match(filterDetail)
.Sort(sort)// optionally
.Skip(skip) // optionally
.Limit(limit) // optionally
.ToList();
It will give you a list of BsonDocument with the given parameters. After that, you have to map with your own detail class.
var resultList = new List<OrderDetails>();
foreach (var master in list)
{
var masterObj = BsonSerializer.Deserialize<dynamic>(master);
foreach (var item in masterObj)
{
if (item.Key == "orderDetails")
{
var mapper = new MapperConfiguration(cfg => { }).CreateMapper();
var retItem = mapper.Map<OrderDetails>(item.Value);
resultList.Add(retItem);
}
}
}

How can I find Index of object in JArray based on a matching search?

I am trying to find the index of an object inside an JArray based on a search.
What I want to end up is the ability to do this
JArray articles = (JArray)DetailedData["product"]["articlesList"][x]
The problem is I dont know how to find what x is
The JSON looks like this ( severly cut down )
"articlesList": [
{
"code": "0587026001",
I want to find the index x where code = 0587026001 but I cannot figure it out
I tried
var index = articlesList.IndexOf("$.[?(#.<code>=='0587026001')]");
but that returned -1
How can I find the index x where code matches as per above?
Hope I understood your question correctly. You can find the Index as following
var result = JObject.Parse(json);
var selectedIndex = result["articlesList"].Select((x,index)=> new { Code= x.Value<string>("code"), Node = x, Index = index})
.Single(x=>x.Code == "0587026003")
.Index;
You can use the SelectTokens to find the relevant tokens based on filter criteria.
The below code selects all the token under articleList which has code equals to '0587026001'
IEnumerable<JToken> tokens = o.SelectTokens("$..articlesList[?(#.Code == '0587026001')]");
Check this dotnet fiddle - https://dotnetfiddle.net/EJvyfp which demonstrates the usage of it.
Sample code:
public class Program
{
public static void Main()
{
JArray o = JArray.Parse(#"[{
'Stores': [
'Lambton Quay',
'Willis Street'
],
'articlesList': [
{
'Code': '0587026001',
'Name': 'Acme Co',
'Products': [
{
'Name': 'Anvil',
'Price': 50
}
]
},
{
'Code': '456',
'Name': 'Contoso',
'Products': [
{
'Name': 'Elbow Grease',
'Price': 99.95
},
{
'Name': 'Headlight Fluid',
'Price': 4
}
]
}
]
},
{
'Stores': [
'ABD',
'XYZ'
],
'articlesList': [
{
'Code': '789',
'Name': 'Acme Co',
'Products': [
{
'Name': 'Anvil',
'Price': 50
}
]
},
{
'Code': '1234',
'Name': 'Contoso',
'Products': [
{
'Name': 'Elbow Grease',
'Price': 99.95
},
{
'Name': 'Headlight Fluid',
'Price': 4
}
]
}
]
}]");
//Console.WriteLine(o);
IEnumerable<JToken> tokens = o.SelectTokens("$..articlesList[?(#.Code == '0587026001')]");
Console.WriteLine(tokens.Count());
Console.WriteLine(tokens.First());
Console.WriteLine("Hello World");
}
}
The above program returns the output as
{
"Code": "0587026001",
"Name": "Acme Co",
"Products": [
{
"Name": "Anvil",
"Price": 50
}
]
}
Here's how you could do it:
private static int GetIndex(string code)
{
string json = "{\"product\": {\"articleList\": [{\"code\": \"0587026001\"}, {\"code\": \"0587026002\"}]}}";
var jObject = JObject.Parse(json);
var articleList = JArray.Parse(jObject["product"]["articleList"].ToString());
var requiredArticle = articleList.First(a => a["code"].ToString().Equals(code));
return articleList.IndexOf(requiredArticle);
}

Filtering out the set difference of two List<JObject> in C# using LINQ

I have 2 List<JObject> to be compared for their set differences,
The format of the two List<JObject> to be compared are as follows:
This is the JSON structure for my base List<JObject> :
[
{
"name": "competency",
"subLists": [
{
"canonicalForm": "c1",
"list": [
"c one",
"c 1",
"cone",
"Cone"
]
}
],
"roles": []
},
{
"name": "demoList",
"subLists": [],
"roles": []
}
]
This is the JSON structure of the List<JObject> that I want to find the set difference against:
[
{
"name": "competency",
"subLists": [
{
"canonicalForm": "c1",
"list": [
"c one",
"c 1",
"cone",
"Cone",
"C ONE"
]
},
{
"canonicalForm": "c2",
"list": [
"c two",
"c 2"
]
}
],
"roles": []
},
{
"name": "leavetype",
"subLists": [
{
"canonicalForm": "annual",
"list": [
"Easter"
]
}
],
"roles": []
},
{
"name": "demoList",
"subLists": [],
"roles": []
}
]
With the output set difference JSON being:
[
{
"name": "competency",
"subLists": [
{
"canonicalForm": "c1",
"list": [
"c one",
"c 1",
"cone",
"Cone",
"C ONE"
]
},
{
"canonicalForm": "c2",
"list": [
"c two",
"c 2"
]
}
],
"roles": []
},
{
"name": "leavetype",
"subLists": [
{
"canonicalForm": "annual",
"list": [
"Easter"
]
}
],
"roles": []
}
]
I have tried using the following code using two for-loops:
List<JObject> baseList = JsonConvert.DeserializeObject<List<JObject>>(baseListString);
List<JObject> comparedList = JsonConvert.DeserializeObject<List<JObject>>(comparedListString);
List<JObject> finalResultList = new List<JObject>();
for (int i = 0; i < baseList.Count; i++)
{
for (int j = 0; i < comparedList.Count; j++)
{
if(baseList[i]["subLists"] != comparedList[j]["subLists"])
finalResultList.Add(comparedList[j]);
}
}
But this is not cutting it, I want to compare the entire JObject based on the key values of name, canonicalForm, list keys and filter out the set difference using LINQ. But I am unable to do so.
I have tried using .Except() extension method but that is not working as well.
Edit:
I want to check if there is any change in the 3 keys which are: name, canonicalForm and list, if there are any change in the compareObject compared to the BaseObject I want to return the JObject which has the change. The output is displayed above.
Any help will be appreciated.
I finally solved my problem by implementing the IEqualityComparer for JObject.
Here is my JObjectEqualityComparer class:
public class JObjectEqualityComparer : IEqualityComparer<JObject>
{
public bool Equals(JObject x, JObject y)
{
if (x == null && y == null)
return true;
if ((x != null && y == null) || (x == null && y != null))
return false;
return JObject.DeepEquals(x, y);
}
public int GetHashCode(JObject obj)
{
JTokenEqualityComparer comparer = new JTokenEqualityComparer();
int hashCode = comparer.GetHashCode(obj);
return hashCode;
}
}
Finally I have used this overload of .Except() extension method to find the Set Difference.
Here is the code:
List<JObject> baseList = JsonConvert.DeserializeObject<List<JObject>>(baseListString);
List<JObject> comparedList = JsonConvert.DeserializeObject<List<JObject>>(comparedListString);
List<JObject> finalResultList = new List<JObject>();
finalResultList = comparedList.Except(baseList, new JObjectEqualityComparer()).ToList();
Thanks to Ed Plunkett, dbc and Coding Yoshi for pointing me out in the correct direction.

c# linq-to-sql EF query to match a particular JSON structure

I've JSON with the following structure:
[
{
"ID": 1,
"Label": "Reg Scheme",
"Colours": [
{
"ID": 1,
"Value": "0x3333cc",
"Result": 1,
"Label": null
},
{
"ID": 2,
"Value": "0x666699",
"Result": 2,
"Label": null
},
{
"ID": 3,
"Value": "0x009966",
"Result": 3,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Spesh Scheme",
"Colours": [
{
"ID": 11,
"Value": "0x59699c",
"Result": 1,
"Label": null
},
{
"ID": 12,
"Value": "0x0070ff",
"Result": 2,
"Label": null
},
{
"ID": 13,
"Value": "0x90865e",
"Result": 3,
"Label": null
}
]
},
and I have an entity dataset whereby I've joined all the relevant information, and am attempting to produce JSON with that structure via a single linq-to-sql EF query to be returned to the webapi method.
My query so far is:
return
DbContext.Schemes
.Join(
DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, sc) => new
{
s.SchemeID,
s.Label,
sc.Colour,
sc.Result,
sc.ColourID
})
.Select(a =>
new Overlay.ReportColourScheme
{
ID = a.SchemeID,
Label = a.Label,
Colours = new List<Overlay.ReportColour>
{
new Overlay.ReportColour
{
ID = a.ColourID,
Value = a.Colour,
Result = a.Result
}
}
})
.ToArray();
Which is almost there but not quite:
[
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 1,
"Value": "0x3333cc",
"Result": 1,
"Label": null
}
]
},
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 2,
"Value": "0x666699",
"Result": 2,
"Label": null
}
]
},
{
"ID": 1,
"Label": "Regular Scheme",
"Colours": [
{
"ID": 3,
"Value": "0x009966",
"Result": 3,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 11,
"Value": "0x59699c",
"Result": 1,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 12,
"Value": "0x0070ff",
"Result": 2,
"Label": null
}
]
},
{
"ID": 2,
"Label": "Protanopia adjusted Scheme",
"Colours": [
{
"ID": 13,
"Value": "0x90865e",
"Result": 3,
"Label": null
}
]
},
As of course it creates a new list for every resultID. The top-level ID is a SchemeID- what I'm looking for is logic along the lines of: "take the first 3 Results with a particular schemeID, add them to a list in Colours, then move on to the next schemeID"
I believe this will produce identical JSON that I started the post with.
Any assistance at all would be greatly appreciated, thank you.
Try the following code:
return
DbContext.Schemes
.Join(
DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, sc) => new
{
s.SchemeID,
s.Label,
sc.Colour,
sc.Result,
sc.ColourID
})
// After joining you group by SchemeID, in this way you have
// for each SchemeID the group of related items
.GroupBy(a => a.SchemeID)
// You then create your result, starting from the main object
.Select(g =>
new Overlay.ReportColourScheme
{
ID = g.Key,
// I suppose you have at least a child for each SchemeID,
// otherwise you can check if the list is empty
Label = g.FirstOrDefault().Label,
// For each group you create a list of child object
Colours = g.Select(v => new Overlay.ReportColour
{
ID = v.ColourID,
Value = v.Colour,
Result = v.Result
}).ToList()
})
.ToArray();
The main issue is that you are using a Join where actually you need a Group Join:
return DbContext.Schemes
.GroupJoin(DbContext.SchemeColours,
s => s.SchemeID,
sc => sc.SchemeID,
(s, colours) => new Overlay.ReportColourScheme
{
ID = s.SchemeID,
Label = s.Label,
Colours = colours
.Select(sc => new Overlay.ReportColour
{
ID = sc.ColourID,
Value = sc.Colour,
Result = sc.Result,
})
.ToList()
})
.ToArray();
But since you are using Entity Framework, it would be much better and eaiser if you define (if you already haven't) and use a navigation property:
class Scheme
{
// ...
public ICollection<SchemeColour> Colours { get; set; }
}
and then simply
return DbContext.Schemes
.Select(s => new Overlay.ReportColourScheme
{
ID = s.SchemeID,
Label = s.Label,
Colours = s.Colours
.Select(sc => new Overlay.ReportColour
{
ID = sc.ColourID,
Value = sc.Colour,
Result = sc.Result,
})
.ToList()
})
.ToArray();

Categories

Resources