C# Backend Pro Is needed ! Data weirdness - c#

Here's my Code
public Tournament Read(int id)
{
using (var context = new DragonLairContext())
{
Tournament tournament = context.Tournaments
.Include(a => a.Game)
.Include(g => g.Game.Genre)
.Include(b => b.Groups.Select(g => g.Teams))
.Include(k => k.Groups.Select(y => y.Teams.Select(l => l.Players)))
.Include(c => c.TournamentType)
.FirstOrDefault(d => d.Id == id);
return tournament;
}
}
I'm using API and due to serialization issues, I need to convert my entities to DTOobject.
Here's a snippet from the Dto Converter
public override DTOTournament Convert(Tournament t)
{
if (t.Game == null || t.TournamentType == null || t.Groups == null) throw new ArgumentException("Missing some data");
DTOTournament dtoTournament = new DTOTournament();
List<DTOGroup> dtoGroups = new List<DTOGroup>();
DTOTournamentType dtoTournamentType = new DTOTournamentType() { Id = t.TournamentType.Id, Type = t.TournamentType.Type };
foreach (var group in t.Groups)
{
if (group.Teams == null) return dtoTournament;
List<DTOTeam> dtoTeams = new List<DTOTeam>();
foreach (var team in group.Teams)
{
List<DTOPlayer> dtoPlayers = new List<DTOPlayer>();
if (team.Players == null) return dtoTournament;
foreach (var player in team.Players)
{
dtoPlayers.Add(new DTOPlayer() { Id = player.Id, Name = player.Name });
}
dtoTeams.Add(new DTOTeam()
{
Id = team.Id,
Name = team.Name,
Win = team.Win,
Loss = team.Loss,
Draw = team.Draw,
DtoPlayers = dtoPlayers
});
}
dtoGroups.Add(new DTOGroup()
{
Id = group.Id,
Name = group.Name,
DtoTeams = dtoTeams,
DtoTournament = dtoTournament
});
}
dtoTournament.DTOTournamentType = dtoTournamentType;
dtoTournament.Id = t.Id;
dtoTournament.Name = t.Name;
dtoTournament.StartDate = t.StartDate;
dtoTournament.DtoGroups = dtoGroups;
dtoTournament.DtoGame = new DTOGame() { Id = t.Game.Id, Name = t.Game.Name, DtoGenre = new DTOGenre() { Id = t.Game.Genre.Id, Name = t.Game.Genre.Name } };
return dtoTournament;
}
Here's the Json
{
"$id": "1",
"Id": 1,
"Name": "I'm a Tournament",
"StartDate": "2015-12-08T00:00:00",
"DTOTournamentType": {
"$id": "2",
"Id": 1,
"Type": "I'm type 2vs2",
"DtoTournaments": null
},
"DtoGroups": [
{
"$id": "3",
"Id": 1,
"Name": "I'm a Group",
"DtoTournament": {
"$ref": "1"
},
"DtoTeams": [
{
"$id": "4",
"Id": 1,
"Draw": 0,
"Loss": 0,
"Win": 0,
"Name": "I'm a Team",
"DtoPlayers": [
{
"$id": "5",
"Id": 1,
"Name": "I'm a Group",
"DtoTeams": null
}
],
"DtoGroups": null
},
{
"$id": "6",
"Id": 2,
"Draw": 0,
"Loss": 0,
"Win": 0,
"Name": "I'm a Team",
"DtoPlayers": [
{
"$id": "7",
"Id": 1,
"Name": "I'm a Group",
"DtoTeams": null
}
],
"DtoGroups": null
}
]
}
],
"DtoGame": {
"$id": "8",
"Id": 1,
"Name": "Im a Game - Wars",
"DtoGenre": {
"$id": "9",
"Id": 1,
"Name": "I'm Genre Roleplaying",
"DtoGames": null
},
"DtoTournaments": null
}
}
My DB contains 3 players - note converted to json
[
{
"$id": "1",
"Id": 1,
"Name": "I'm player Søren",
"DtoTeams": [
{
"$id": "2",
"Id": 1,
"Draw": 0,
"Loss": 0,
"Win": 0,
"Name": "I'm a Team",
"DtoPlayers": null,
"DtoGroups": null
}
]
},
{
"$id": "3",
"Id": 2,
"Name": "I'm player Mark",
"DtoTeams": [
{
"$id": "4",
"Id": 1,
"Draw": 0,
"Loss": 0,
"Win": 0,
"Name": "I'm a Team",
"DtoPlayers": null,
"DtoGroups": null
}
]
},
{
"$id": "5",
"Id": 3,
"Name": "I'm player René",
"DtoTeams": [
{
"$id": "6",
"Id": 2,
"Draw": 0,
"Loss": 0,
"Win": 0,
"Name": "I'm a Team",
"DtoPlayers": null,
"DtoGroups": null
}
]
}
]
So my problem which I really can't figure out. How come my DtoPlayer.Name has the same name as DtoGroup.Name. Have a look at my includes.

This exception is thrown because you are trying to use a property that was not retrieved from the database after you have disposed the database context. You can see the data while debugging because the variable is defined inside the using scope. You should also fetch the Teams on Group class.
using (var context = new DragonLairContext())
{
Tournament tournament = context.Tournaments
.Include(a => a.Game)
.Include(g => g.Game.Genre)
.Include(b => b.Groups.Select(g => g.Teams))
.Include(c => c.TournamentType)
.FirstOrDefault(d => d.Id == id);
return tournament;
}

Since you said that you could see data in debug mode, try this method.
Tournament tournament = context.Tournaments
.Include(a => a.Game)
.Include(g => g.Game.Genre)
.Include(b => b.Groups)
.Include(c => c.TournamentType).ToList()
.FirstOrDefault(d => d.Id == id);

Related

Calculate the cumulative sum of a grouped collection

This is the expected result: The accumulatedMaxPax should be equal to the previous value + the current maxPax. It should reset (=0) on every destinations change.
[
{
"date": "2022-05-06",
"destinations": [
{
"id": 1,
"description": "PAXOS - ANTIPAXOS",
"abbreviation": "PA",
"ports": [
{
"id": 1,
"description": "CORFU PORT",
"maxPax": 200,
"accumulatedMaxPax": 200 // Initial (accumulatedMaxPax = maxPax )
},
{
"id": 4,
"description": "BENITSES PORT",
"maxPax": 200,
"accumulatedMaxPax": 400 // 200 (previous) + 200 (current) = 400
},
{
"id": 17,
"description": "BOUKARI PORT",
"maxPax": 100,
"accumulatedMaxPax": 500 // 400 (previous) + 100 (current) = 500
},
{
"id": 2,
"description": "LEFKIMMI PORT",
"maxPax": 400,
"accumulatedMaxPax": 900 // 500 (previous) + 400 (current) = 900
}
]
},
{
"id": 3,
"description": "BLUE LAGOON",
"abbreviation": "BL",
"ports": [
{
"id": 1,
"description": "CORFU PORT",
"maxPax": 215,
"accumulatedMaxPax": 215
},
{
"id": 4,
"description": "BENITSES PORT",
"maxPax": 215,
"accumulatedMaxPax": 430
}
]
},
{
"id": 7,
"description": "PARTY",
"abbreviation": "PRT",
"ports": [
{
"id": 1,
"description": "CORFU PORT",
"maxPax": 200,
"accumulatedMaxPax": 200
}
]
}
]
}
]
This is the code which works according to the specs, with the missing part of the puzzle as question marks!
public IEnumerable<AvailabilityCalendarGroupVM> GetForCalendar(string fromDate, string toDate) {
return context.Schedules
.Where(x => x.Date >= Convert.ToDateTime(fromDate) && x.Date <= Convert.ToDateTime(toDate))
.GroupBy(x => x.Date)
.Select(x => new AvailabilityCalendarGroupVM {
Date = DateHelpers.DateTimeToISOString(x.Key.Date),
Destinations = x.GroupBy(x => new { x.Date, x.Destination.Id, x.Destination.Description, x.Destination.Abbreviation }).Select(x => new DestinationCalendarVM {
Id = x.Key.Id,
Description = x.Key.Description,
Abbreviation = x.Key.Abbreviation,
Ports = x.GroupBy(x => new { x.PortId, x.Port.Description, x.Port.Abbreviation, x.MaxPax, x.Port.StopOrder }).OrderBy(x => x.Key.StopOrder).Select(x => new PortCalendarVM {
Id = x.Key.PortId,
Description = x.Key.Description,
MaxPax = x.Key.MaxPax,
AccumulatedMaxPax = NOW WHAT ???
})
})
}).ToList();
}

LINQ nested selects is this the most efficient query?

For educational purposes, I’m rewriting an old MVC web app I created as a web API and trying to get a richer understanding of LINQ. I’m trying to find out the most efficient way to query and sort data from multiple tables using LINQ in code-first EF.
I have a Shift table that holds information for a scheduled work shift. Each shift can have multiple employees assigned (Shift --<ShiftEmployees>-- Employee) as well as multiple Activities in various locations, various dress attire and various start times (Shift --<ShiftActivities>-- Activity/Location/ Uniform).
The following basic query yields this result:
var shift = _context.Shifts
.Include(s => s.Platoon)
.Include(s => s.ShiftActivities).ThenInclude(sa => sa.Activity)
.Include(s => s.ShiftActivities).ThenInclude(sa => sa.Uniform)
.Include(s => s.ShiftActivities).ThenInclude(sa => sa.Location)
.Include(s => s.ShiftEmployees).ThenInclude(se => se.Employee).ThenInclude(e => e.Rank)
.FirstOrDefault(s => s.ShiftID == id);
{
"shiftID": 1,
"shiftDate": "2018-09-03T00:00:00",
"information": null,
"platoon": {
"platoonID": 2,
"platoonName": "B Platoon"
},
"shiftEmployees": [
{
"shiftEmployeeID": 1,
"userID": null,
"pinStatus": 3,
"employee": {
"employeeID": 1,
"serialNumber": "34567",
"lastName": "Test2",
"firstName": "Employee",
"notifyEmail": false,
"receiptEmail": false,
"notifySMS": false,
"receiptSMS": false,
"rank": {
"rankID": 1,
"rankName": "Paygrade III",
"shortName": "P-III",
"accessLevel": 1
},
"platoon": {
"platoonID": 2,
"platoonName": "B Platoon"
}
}
},
{
"shiftEmployeeID": 2,
"userID": null,
"pinStatus": 3,
"employee": {
"employeeID": 2,
"serialNumber": "12345",
"lastName": "Test",
"firstName": "Employee",
.....Other fields omitted for brevity....
"rank": {
.....Other fields omitted for brevity....
},
"platoon": {
.....Other fields omitted for brevity....
}
}
}
],
"shiftActivities": [
{
"shiftActivityID": 2,
"startTime": "12:50:05",
"activity": {
"activityID": 2,
"activityName": "1st Muster"
},
"location": {
"locationID": 2,
"locationCode": 99,
"locationName": "Location 2",
"locationAddress": null,
"locationCity": null,
"locationZip": null,
"latitude": null,
"longitude": null
},
"uniform": {
"uniformID": 1,
"uniformName": "A Uniform",
"uniformDescription": null
}
},
{
"shiftActivityID": 3,
"startTime": "15:28:25",
"activity": {
.....Other fields omitted for brevity....
},
"location": {
.....Other fields omitted for brevity....
},
"uniform": {
.....Other fields omitted for brevity....
}
},
{
Additional `shiftActivity` omitted for brevity
}
]
}
This returns the needed data, however, I want to sort the ShiftActivities according to start time, and also sort the ShiftEmployees by Rank, then LastName, then SerialNumber. So, I came up with the following query:
var shift = _context.Shifts
.Include(s => s.Platoon)
.Select(s => new
{
shift = s,
shiftEmployees = s.ShiftEmployees.Select(se => new
{
_shiftEmployee = se,
employee = se.Employee,
rank = se.Employee.Rank
}).OrderBy(se => se.employee.LastName)
.ThenBy(se => se.employee.SerialNumber),
shiftActivities = s.ShiftActivities.Select(sa => new
{
_shiftActivity = sa,
activity = sa.Activity,
location = sa.Location,
uniform = sa.Uniform,
}).OrderBy(sa => sa._shiftActivity.StartTime)
})
.OrderBy(s => s.shift.Platoon)
.FirstOrDefault(s => s.shift.ShiftID == id);
{
"shift": {
"shiftID": 1,
"shiftDate": "2018-09-03T00:00:00",
"information": null,
"platoon": {
"platoonID": 2,
"platoonName": "B Platoon"
},
"shiftEmployees": null,
"shiftActivities": null
},
"shiftEmployees": [
{
"_shiftEmployee": {
"shiftEmployeeID": 1,
"userID": null,
"pinStatus": 3,
"employee": null
},
"employee": {
"employeeID": 1,
"serialNumber": "34567",
"lastName": "Test2",
"firstName": "Employee",
"notifyEmail": false,
"receiptEmail": false,
"notifySMS": false,
"receiptSMS": false,
"rank": null,
"platoon": null
},
"rank": {
"rankID": 1,
"rankName": "Paygrade III",
"shortName": "P-III",
"accessLevel": 1
}
},
{
"_shiftEmployee": {
.....Other fields omitted for brevity....
},
"employee": {
.....Other fields omitted for brevity....
},
"rank": {
.....Other fields omitted for brevity....
}
}
],
"shiftActivities": [
{
"_shiftActivity": {
"shiftActivityID": 2,
"startTime": "12:50:05",
"activity": null,
"location": null,
"uniform": null
},
"activity": {
"activityID": 2,
"activityName": "1st Muster"
},
"location": {
"locationID": 2,
"locationCode": 99,
"locationName": "Location 2",
"locationAddress": null,
"locationCity": null,
"locationZip": null,
"latitude": null,
"longitude": null
}
"uniform": {
"uniformID": 1,
"uniformName": "A Uniform",
"uniformDescription": null
}
},
{
"_shiftActivity": {
.....Other fields omitted for brevity....
},
"activity": {
.....Other fields omitted for brevity....
},
"location": {
.....Other fields omitted for brevity....
},
"uniform": {
.....Other fields omitted for brevity....
}
},
{
Additional `shiftActivity` omitted for brevity
}
]
}
To my human eyes, the first query seems more organized and easier to read, although it’s unsorted the way it needs to be. The second query yields 200 bytes more data (negligible for the low amount of traffic the site would get) and the return times are within a few milliseconds, according to PostMan.
Is my second query the most efficient method to achieve the stated goals? I suppose my desired result would be to sort the data like in the second query while maintaining the structure and readability of the first.
Also, since both approaches pull more data than the user will see (e.g. notifyEmail/SMS fields and Platoon information on Employee, accessLevel in the Rank, etc.) is it recommended to use a ViewModel (or data model, since this an API) and only populate the fields I need? ViewModels work well in MVC, but are they necessary in a web API (without getting too far off topic, but I understand if this should be a separate question)? What would be the best practice in this case?

ElasticSearch - different result ordering for simple request and aggregation request (NEST)

I have a search page which contains two search result types: summary result and concrete result.
Summary result page contains top 3 result per category (top hits)
Concrete result page contains all result for a selected category.
To obtain the Summary page I use the request:
var searchDescriptor = new SearchDescriptor<ElasticType>();
searchDescriptor.Index("index_name")
.Query(q =>
q.MultiMatch(m => m
.Fields(fs => fs
.Field(f => f.Content1, 3)
.Field(f => f.Content2, 2)
.Field(f => f.Content3, 1))
.Fuzziness(Fuzziness.EditDistance(1))
.Query(query)
.Boost(1.1)
.Slop(2)
.PrefixLength(1)
.MaxExpansions(100)
.Operator(Operator.Or)
.MinimumShouldMatch(2)
.FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean)
.TieBreaker(1.0)
.CutoffFrequency(0.5)
.Lenient()
.ZeroTermsQuery(ZeroTermsQuery.All))
&& (q.Terms(t => t.Field(f => f.LanguageId).Terms(1)) || q.Terms(t => t.Field(f => f.LanguageId).Terms(0))))
.Aggregations(a => a
.Terms("category", tagd => tagd
.Field(f => f.Category)
.Size(10)
.Aggregations(aggs => aggs.TopHits("top_tag_hits", t => t.Size(3)))))
.FielddataFields(fs => fs
.Field(p => p.Content1, 3)
.Field(p => p.Content2, 2)
.Field(p => p.Content3, 1));
var elasticResult = _elasticClient.Search<ElasticType>(_ => searchDescriptor);
And I get result, for example
{
"aggregations": {
"category": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [{
"key": "category1",
"doc_count": 40,
"top_tag_hits": {
"hits": {
"total": 40,
"max_score": 5.4,
"hits": [{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 5.4,
"_source": {
"id": 1
}
},
{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 4.3,
"_source": {
"id": 3 // FAIL!
}
},
{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 4.3,
"_source": {
"id": 2
}
}]
}
}
}]
}
}
}
So i get few hits with the same _score.
To obtain the concrete result (by category) page I use the request:
var searchDescriptor = new SearchDescriptor<ElasticType>();
searchDescriptor.Index("index_name")
.Size(perPage <= 0 ? 100 : perPage)
.From(page * perPage)
.Query(q => q
.MultiMatch(m => m
.Fields(fs => fs
.Field(f => f.Content1, 3)
.Field(f => f.Content2, 2)
.Field(f => f.Content3, 1)
.Field(f => f.Category))
.Fuzziness(Fuzziness.EditDistance(1))
.Query(searchRequest.Query)
.Boost(1.1)
.Slop(2)
.PrefixLength(1)
.MaxExpansions(100)
.Operator(Operator.Or)
.MinimumShouldMatch(2)
.FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean)
.TieBreaker(1.0)
.CutoffFrequency(0.5)
.Lenient()
.ZeroTermsQuery(ZeroTermsQuery.All))
&& q.Term(t => t.Field(f => f.Category).Value(searchRequest.Category))
&& (q.Terms(t => t.Field(f => f.LanguageId).Terms(1)) || q.Terms(t => t.Field(f => f.LanguageId).Terms(0))))
.FielddataFields(fs => fs
.Field(p => p.Content1, 3)
.Field(p => p.Content2, 2)
.Field(p => p.Content3, 1))
.Aggregations(a => a
.Terms("category", tagd => tagd
.Field(f => f.Category)));
And the result something like this:
{
"hits": {
"total": 40,
"max_score": 7.816723,
"hits": [{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 7.816723,
"_source": {
"id": 1
}
},
{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 6.514713,
"_source": {
"id": 2
}
},
{
"_index": "...",
"_type": "...",
"_id": "...",
"_score": 6.514709,
"_source": {
"id": 3
}
}]
}
}
And so in the second case, for a specific category I get the _score with great precision and elastic can easily sort the results correctly. But in the case of aggregation there are results with the same _score, and in this case, the sorting is not clear how it works.
Can someone direct me to the right path how to solve this problem? or how can I achieve the same order in the results? Maybe I can increase the accuracy for the aggregated results?
I use elasticsearch server version "5.3.0" and NEST library version "5.0.0".
Update:
Native query for aggregation request:
{
"fielddata_fields": [
"content1^3",
"content2^2",
"content3^1"
],
"aggs": {
"category": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"top_tag_hits": {
"top_hits": {
"size": 3
}
}
}
}
},
"query": {
"bool": {
"must": [
{
"multi_match": {
"boost": 1.1,
"query": "sparta",
"fuzzy_rewrite": "constant_score_boolean",
"fuzziness": 1,
"cutoff_frequency": 0.5,
"prefix_length": 1,
"max_expansions": 100,
"slop": 2,
"lenient": true,
"tie_breaker": 1.0,
"minimum_should_match": 2,
"operator": "or",
"fields": [
"content1^3",
"content2^2",
"content3^1"
],
"zero_terms_query": "all"
}
},
{
"bool": {
"should": [
{
"terms": {
"languageId": [
1
]
}
},
{
"terms": {
"languageId": [
0
]
}
}
]
}
}
]
}
}
}
Native query for concrete request:
{
"from": 0,
"size": 100,
"fielddata_fields": [
"content1^3",
"content2^2",
"content3^1"
],
"aggs": {
"category": {
"terms": {
"field": "category"
}
}
},
"query": {
"bool": {
"must": [
{
"bool": {
"must": [
{
"multi_match": {
"boost": 1.1,
"query": ".....",
"fuzzy_rewrite": "constant_score_boolean",
"fuzziness": 1,
"cutoff_frequency": 0.5,
"prefix_length": 1,
"max_expansions": 100,
"slop": 2,
"lenient": true,
"tie_breaker": 1.0,
"minimum_should_match": 2,
"operator": "or",
"fields": [
"content1^3",
"content2^2",
"content3^1",
"category"
],
"zero_terms_query": "all"
}
},
{
"term": {
"category": {
"value": "category1"
}
}
}
]
}
},
{
"bool": {
"should": [
{
"terms": {
"languageId": [
1
]
}
},
{
"terms": {
"languageId": [
0
]
}
}
]
}
}
]
}
}
}
Also i use next mapping for creating index:
var descriptor = new CreateIndexDescriptor(indexName)
.Mappings(ms => ms
.Map<ElasticType>(m => m
.Properties(ps => ps
.Keyword(s => s.Name(ecp => ecp.Title))
.Text(s => s.Name(ecp => ecp.Content1))
.Text(s => s.Name(ecp => ecp.Content2))
.Text(s => s.Name(ecp => ecp.Content3))
.Date(s => s.Name(ecp => ecp.Date))
.Number(s => s.Name(ecp => ecp.LanguageId).Type(NumberType.Integer))
.Keyword(s => s.Name(ecp => ecp.Category))
.Text(s => s.Name(ecp => ecp.PreviewImageUrl).Index(false))
.Text(s => s.Name(ecp => ecp.OptionalContent).Index(false))
.Text(s => s.Name(ecp => ecp.Url).Index(false)))));
_elasticClient.CreateIndex(indexName, _ => descriptor);
Your query has problems.
What you are using is combination of must and should inside a must as part of bool query.
So if you read more in this link, you can see for must
The clause (query) must appear in matching documents and will contribute to the score.
so it will five equal scoring to all your documents which matched the condition. Any other condition which didn't match the condition won't even be there in results to score.
What you should do it use should query but outside of must query, so Elasticsearch will be able to score your documents correctly
For more info as part of this question
Can someone direct me to the right path how to solve this problem?
you should pass 'explain': true in the query. You can read more about explain query and how to interpret results in this link.
You answer for this question is
how can I achieve the same order in the results?
As every score is same therefore Elasticsearch can sort the result in any way it gets the response from its nodes.
Possible Solution:
You should reorganize your query to make real use of should query and its boosting capabilities. You can read more about boosting here.
I tried two query similar to yours but with correct usage of should and they gave me same order as expected. Your both query should be constructed as below:
{
"from": 0,
"size": 10,
"_source": [
"content1^3",
"content2^2",
"content3^1"
],
"query": {
"bool": {
"should": [
{
"match": {
"languageId": 1
}
},
{
"match": {
"languageId": 0
}
}
],
"must": [
{
"multi_match": {
"boost": 1.1,
"query": ".....",
"fuzzy_rewrite": "constant_score_boolean",
"fuzziness": 1,
"cutoff_frequency": 0.5,
"prefix_length": 1,
"max_expansions": 100,
"slop": 2,
"lenient": true,
"tie_breaker": 1,
"minimum_should_match": 2,
"operator": "or",
"fields": [
"content1^3",
"content2^2",
"content3^1",
"category"
],
"zero_terms_query": "all"
}
}
]
}
}
}
and second query as
{
"size": 0,
"query": {
"bool": {
"should": [
{
"match": {
"languageId": 1
}
},
{
"match": {
"languageId": 0
}
}
],
"must": [
{
"multi_match": {
"boost": 1.1,
"query": ".....",
"fuzzy_rewrite": "constant_score_boolean",
"fuzziness": 1,
"cutoff_frequency": 0.5,
"prefix_length": 1,
"max_expansions": 100,
"slop": 2,
"lenient": true,
"tie_breaker": 1,
"minimum_should_match": 2,
"operator": "or",
"fields": [
"content1^3",
"content2^2",
"content3^1",
"category"
],
"zero_terms_query": "all"
}
}
]
}
},
"aggs": {
"categories": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"produdtcs": {
"top_hits": {
"_source": [
"content1^3",
"content2^2",
"content3^1"
],
"size": 3
}
}
}
}
}
}

Generic method to convert a flat JSON array to nested JSON

I have a JSON object as below
[
{
"Id": 7,
"Name": "Colocation Folder",
"ParentId": 1,
"depth": 0
},
{
"Id": 8,
"Name": "CoLo Real Estate",
"ParentId": 7,
"depth": 1
},
{
"Id": 10,
"Name": "CoLo: Burst",
"ParentId": 7,
"depth": 1
},
{
"Id": 34,
"Name": "CoLo Dedicated Bandwidth",
"ParentId": 7,
"depth": 1
},
{
"Id": 10035,
"Name": "Infrastructure as a Service",
"ParentId": 7,
"depth": 1
},
{
"Id": 10037,
"Name": "Software as a Service",
"ParentId": 7,
"depth": 1
},
{
"Id": 10038,
"Name": "IaaS Component Upgrade",
"ParentId": 7,
"depth": 1
},
{
"Id": 668,
"Name": "CoLo Misc Folder",
"ParentId": 7,
"depth": 1
},
{
"Id": 758,
"Name": "CoLo: Conduit Fee",
"ParentId": 668,
"depth": 2
},
{
"Id": 765,
"Name": "CoLo: Private VLAN",
"ParentId": 668,
"depth": 2
}
]
The Id and ParentId fields show the relation between the items. I need to make it as a nested JSON using C#.
Since there will be many such models, I don't want to create individual classes for each model. Is there a generic approach in C# that will take a flat JSON array, take the ID and ParentId fields as input and then return me a nested JSON with all other fields in the array as well? For example, I am looking for an output of nested JSON as below:
[
{
"Id": 7,
"Name": "Colocation Folder",
"items": [
{
"Id": 8,
"Name": "CoLo Real Estate",
"ParentId": 7
},
{
"Id": 10,
"Name": "CoLo: Burst",
"ParentId": 7
},
{
"Id": 34,
"Name": "CoLo Dedicated Bandwidth",
"ParentId": 7
},
{
"Id": 10035,
"Name": "Infrastructure as a Service",
"ParentId": 7
},
{
"Id": 10037,
"Name": "Software as a Service",
"ParentId": 7
},
{
"Id": 10038,
"Name": "IaaS Component Upgrade",
"ParentId": 7
},
{
"Id": 668,
"Name": "CoLo Misc Folder",
"ParentId": 7,
"items": [
{
"Id": 758,
"Name": "CoLo: Conduit Fee",
"ParentId": 668
},
{
"Id": 765,
"Name": "CoLo: Private VLAN",
"ParentId": 668
}
]
}
]
}
]
If you use Json.Net, you can do this conversion in a generic way using the LINQ-to-JSON API (JObjects). The idea is to parse the JSON array and add all the individual items to a dictionary keyed by Id. Then, loop over the dictionary items, and for each one, try to look up the parent. If the parent is found, add the item to the parent's items array (creating it if needed). Otherwise, add the item to the root array. Along the way, remove the depth property from each item, since you don't seem to want that in the output. Lastly, just dump the root array to string to get the final result.
var dict = JArray.Parse(json)
.Children<JObject>()
.ToDictionary(jo => (string)jo["Id"], jo => new JObject(jo));
var root = new JArray();
foreach (JObject obj in dict.Values)
{
JObject parent;
string parentId = (string)obj["ParentId"];
if (parentId != null && dict.TryGetValue(parentId, out parent))
{
JArray items = (JArray)parent["items"];
if (items == null)
{
items = new JArray();
parent.Add("items", items);
}
items.Add(obj);
}
else
{
root.Add(obj);
}
JProperty depth = obj.Property("depth");
if (depth != null) depth.Remove();
}
Console.WriteLine(root.ToString());
Fiddle: https://dotnetfiddle.net/Buza6T
You can use a dynamic object with JSON.Net like so to detect your properties dynamically then you could build a new json object with the desired nesting:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
dynamic d = JArray.Parse(stringy);
foreach(var ob in d)
{
if(ob.ParentID != ob.Id)
{
string debug = "oh snapple, it's a child object";
}
}
Share my working code for you at jsFiddle full source code
recursive function is:
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
full source code:
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].ParentId == parent) {
var items = getNestedChildren(arr, arr[i].Id)
if(items.length) {
arr[i].items = items
}
out.push(arr[i])
}
}
return out
}
var flat_array = [
{
"Id": 7,
"Name": "Colocation Folder",
"ParentId": 1,
"depth": 0
},
{
"Id": 8,
"Name": "CoLo Real Estate",
"ParentId": 7,
"depth": 1
},
{
"Id": 10,
"Name": "CoLo: Burst",
"ParentId": 7,
"depth": 1
},
{
"Id": 34,
"Name": "CoLo Dedicated Bandwidth",
"ParentId": 7,
"depth": 1
},
{
"Id": 10035,
"Name": "Infrastructure as a Service",
"ParentId": 7,
"depth": 1
},
{
"Id": 10037,
"Name": "Software as a Service",
"ParentId": 7,
"depth": 1
},
{
"Id": 10038,
"Name": "IaaS Component Upgrade",
"ParentId": 7,
"depth": 1
},
{
"Id": 668,
"Name": "CoLo Misc Folder",
"ParentId": 7,
"depth": 1
},
{
"Id": 758,
"Name": "CoLo: Conduit Fee",
"ParentId": 668,
"depth": 2
},
{
"Id": 765,
"Name": "CoLo: Private VLAN",
"ParentId": 668,
"depth": 2
}
]
var nested = getNestedChildren(flat_array, 1)
console.log(nested)

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