I am working on a Web API in C# and am getting my data from a SQL Database. When I put a single student number in the GET call, their data is return correctly, however if they have two rows in the SQL query, then Return in the GET call shows their data in two separate properties. Is there a way to combine List Objects for a user who has two different rows of data into one single return. For instance, the below User belongs to two departments (Politics and Religions), and when I call the GET method, they appear twice, once for each Department;
{
"roles": [
[
"STUDENT",
"UG",
2,
"Politics",
"Smith, John",
123456
],
[
"STUDENT",
"UG",
2,
"Religions",
"Smith, John",
123456
]
]
}
What I would like to be return is this;
{
"roles": [
[
"STUDENT",
"UG",
2,
"Politics",
"Religions",
"Smith, John",
123456
]
]
}
I am retrieving the data from a SQL Server DB via a query, and have created a list to which I am adding the data from the query as Objects. When I search for a student using their ID, and if they have two different departments, I'd like for the 2 properties to be returned as a single one.
I have attempted the below, but this is as far as I've gotten;
int howmanyrecords = studentList.Count;
if (howmanyrecords >1)
{
foreach(Object stu in studentList)
{
var firstObject = studentList.ElementAt(0);
}
}
The above studentList.ElementAt(0) retrieves the first instance in the studentList i.e. the entry which has Politics as the department, but I'm not sure how to complete the above and how to merge into one object - my initial thought is, to compare all the indexes of both Objects and if they're different, add the value at Index X from Object2 into Object1.
Any pointers would be appreciated.
this code you want
var jObj = JObject.Parse(json);
var roles = (JArray)jObj["roles"];
if (roles.Count() > 1)
{
jObj["roles"] = new JArray(roles[0].Union(roles[1]));
json = jObj.ToString();
}
IMHO this code you need
if (roles.Count() > 1)
{
for (int i = 0; i < roles[0].Count(); i++)
{
if (roles[0][i].ToString() != roles[1][i].ToString())
{
var arr = new JArray();
arr.Add(roles[0][i]);
arr.Add(roles[1][i]);
roles[0][i] = arr;
}
}
roles[1].Remove();
json = jObj.ToString();
}
output
{
"roles": [
[
"STUDENT",
"UG",
2,
[
"Politics",
"Religions"
],
"Smith, John",
123456
]
]
UPDATE
This will be working for any number of students
var jObj = JObject.Parse(json);
var roles = (JArray)jObj["roles"];
if (roles.Count() > 1)
{
for (int j = 0; j < roles[0].Count(); j++)
{
var jArr = new JArray();
for (var i = 1; i < roles.Count(); i++)
{
if (i == 1) jArr = new JArray { roles[0][j] };
jArr.Add(roles[i][j]);
}
var arr = jArr.Distinct();
if (arr.Count() > 1) roles[0][j] = new JArray(arr); // ["Politics", "Religions"]
//or if you dont want an array
if (arr.Count() > 1) roles[0][j] = string.Join(",", arr); // "Politics,Religions"
}
var c = roles.Count();
for (var i = c - 1; i > 0; i--)
roles[i].Remove();
json = jObj.ToString().Dump();
}
Among companies in the same industry, I need to take the top 5 companies with the highest revenue, and which particular company ranks in that industry.
It is easy to write the first query:
GET myIndex/_search
{
"from": 0,
"size": 5,
"query": {
"match": {
"industryCode": "xxxx"
}
},
"sort": [
{
"revenue": {
"order": "desc"
}
}
]
}
But I don't know how to write the second query. Currently, I have to use the scroll function to scan all records of companies in the same industry, like this:
async Task<int> GetRank()
{
int rank = 0;
searchRequest.Size = 500;
searchRequest.From = 0;
searchRequest.Scroll = "1m";
var rs = await _elasticClient.SearchAsync<Tmp>(searchRequest);
while (rs.Documents.Count > 0)
{
foreach (var item in rs.Documents)
{
rank++;
if (item.OrganCode == request.OrganCode) return rank;
}
rs = _elasticClient.Scroll<Tmp>("1m", rs.ScrollId);
}
return rank;
}
This approach is really very slow, if the company has very low revenue, it may take several minutes to produce results. Is there any way to solve this problem? Thank you very much!!!
If I understand your question correctly, you want to get the top 5 companies with the highest revenue, grouped by industry code. This can be done with a terms aggregation and a top_hits sub aggregation
{
"aggs": {
"industry_codes": {
"aggs": {
"top_companies": {
"top_hits": {
"size": 5,
"sort": [
{
"revenue": {
"order": "desc"
}
}
]
}
}
},
"terms": {
"field": "industryCode"
}
}
},
"size": 0
}
In NEST, this would look something like
var client = new ElasticClient(settings);
var searchResponse = client.Search<Tmp>(s => s
.Size(0)
.Aggregations(a => a
.Terms("industry_codes", t => t
.Field(f => f.IndustryCode)
.Aggregations(aa => aa
.TopHits("top_companies", th => th
.Sort(so => so
.Descending(f => f.Revenue)
)
.Size(5)
)
)
)
)
);
To get the top hits for each industry code
var termsAgg = searchResponse.Aggregations.Terms("industry_codes");
foreach (var bucket in termsAgg.Buckets)
{
var topHits = bucket.TopHits("top_companies");
foreach (var company in topHits.Documents<Tmp>())
{
// do something with company
}
}
The following query returns duplicate results, in the second select query.
Country has 0..1 to * relationship with leagues.
Leagues have 1 to * relationship with userLeagues.
return from ul in userLeagues
select new Map.Country
{
id = ul.Country.CountryId,
name = ul.Country.Common_Name,
leagues = userLeagues.Where(x => x.CountryId.Value == ul.CountryId.Value)
.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};
I tried using Distinct with no luck.
It seems that either i have to use distinct or groupby countryId
The output is such as
[
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
},
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
}
]
You need to group it by CountryId and Common_Name to get expected results:
var result = from ul in userLeagues
group ul by new { ul.Country.CountryId, ul.Country.Common_Name } into g
select new Map.Country
{
id = g.Key.CountryId,
name = g.Key.Common_Name,
leagues = g.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};
Think about what you're doing: For each league in userLeagues, you're creating a Map.Country for the country that league belongs to. If three leagues are in France, that's three Frances. France is a wonderful country, but let's not go overboard.
Instead, you want to start with a distinct list of countries. For each one, create one Map.Country, and give that Map.Country a list of the leagues that should belong to it.
First, let's make Country implement IEquatable<Country> for Distinct purposes:
public class Country : IEquatable<Country>
{
public bool Equals(Country other)
{
return other.CountryID == CountryID;
}
Second, you want to start with a distinct list of countries, and then populate them with leagues.
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct()
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => x.Country == ctry)
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
I didn't recreate your Map.League and Map.Country classes, I just used anonymous objects, and I left it that way because this code definitely works just as it is. But filling in your class names is trivial.
If it's not practical to make Country implement IEquatable<T>, just write a quick equality comparer and use that:
public class CountryComparer : IEqualityComparer<Country>
{
public bool Equals(Country x, Country y)
{
return x.CountryID == y.CountryID;
}
public int GetHashCode(Country obj)
{
return obj.CountryID.GetHashCode();
}
}
...like so:
var cc = new CountryComparer();
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct(cc)
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => cc.Equals(x.Country, ctry))
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
This is logically equivalent to a GroupBy, which is probably a more respectable way to do it. But somebody else thought of that before I did, so he earned the glory.
I would say the you need to reverse your query. So instead of starting with userLeagues, start with country and include the child leagues.
This is going to be a two part question.
I am trying to build a data structure for use with the Google Charts API (specifically, their data table).
Here is my code as it stands now:
return Json.Encode(
RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4) //Only grab the last 4 years worth of RMAs
.GroupBy(r => new { Problem = r.Problem, Year = r.CreatedDate.Year, Quarter = ((r.CreatedDate.Month) / 3) })
.Select(r => new { Problem = r.Key.Problem, Year = r.Key.Year, Quarter = r.Key.Quarter, Count = r.Count() })
);
This gets me very close. This gets me an array similar to the following:
{"Problem":"It broke!","Year":2012,"Quarter":2,"Count":3},
{"Problem":"It broke!","Year":2012,"Quarter":1,"Count":1}
But, what I want is for the data to be grouped further by the "Problem" property so that the quarter is an array for each problem (this makes the data structure much easier to iterate over). An example of the desired structure:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":1,"Count":31}
}
The second part of the question: How can I ensure that I have data for each quarter (again, this makes it much easier to iterate over for building the data table with the API), even if a "Problem" did not occur in that quarter? Using the same example as last time:
{"Problem":"It broke!",
{"Year":2012,"Quarter":2,"Count":3},
{"Year":2012,"Quarter":1,"Count":1}
},
{"Problem":"Some other problem",
{"Year":2012,"Quarter":2,"Count":0}
{"Year":2012,"Quarter":1,"Count":31}
}
Thanks to Mr. TA for the inspiration and for showing me that you can use LINQ against a grouping.
I have tested this out in a local environment and the LINQ does indeed return a list of Problems tied to an array of Year/Quarter groupings with a total Count. I don't know if Json.Encode encodes it in the correct format though.
The following LINQ should return an anonymous type that fits the format you needed:
Edit: Query now returns count=0 for quarters where at least one problem occurred, but specified problem did not occur
var quarters = RMAs
.Where(rma => rma.CreatedDate.Year > DateTime.Now.Year - 4)
.GroupBy(rma => new {
Year = rma.CreatedDate.Year,
Quarter = ((rma.CreatedDate.Month) / 3)
});
return Json.Encode(
RMAs
//Only grab the last 4 years worth of RMAs
.Where(r => r.CreatedDate.Year > DateTime.Now.Year - 4)
// Group all records by problem
.GroupBy(r => new { Problem = r.Problem })
.Select(grouping => new
{
Problem = grouping.Key.Problem,
Occurrences = quarters.Select(quarter => new
{
Year = quarter.Key.Year,
Quarter = quarter.Key.Quarter,
Count = grouping
.GroupBy(record => new
{
Year = record.CreatedDate.Year,
Quarter = ((record.CreatedDate.Month) / 3)
})
.Where(record =>
record.Key.Year == quarter.Key.Year
&& record.Key.Quarter == quarter.Key.Quarter
).Count()
}).ToArray()
}));
Update: Thanks to JamieSee for updating with example JSON output:
This is an example of the JSON output:
[{"Problem":"P","Occurrences":[{"Year":2012,"Quarter":4,"Count":2},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]},{"Problem":"Q","Occurrences":[{"Year":2012,"Quarter":3,"Count":1},{"Year":2012,"Quarter":2,"Count":1},{"Year":2012,"Quarter":1,"Count":1}]}]
Add the following to your query:
.GroupBy(x => x.Problem)
.ToDictionary(g => g.Key, g => g.Select(x=>new { Year=x.Year, Quarter=x.Quarter, Count = x.Count }));
You have to insert the following before .ToDictionary() above:
.Select(g =>
new {
Key = g.Key,
Items =
g
.GroupBy(r => r.Year)
.SelectMany(gy =>
gy.Concat(
Enumerable.Range(1,5)
.Where(q => !gy.Any(r=>r.Quarter == q))
.Select(q => new { Problem = g.Key, Year = gy.Key, Quarter = q, Count = 0 })
)
)
}
)
I think... try it out :)
I would advise against following this approach, however, and create "empty" records on the client, to avoid excessive bandwidth use.
Here's the full restatement to meet all your criteria:
public static IEnumerable<DateTime> GetQuarterDates()
{
for (DateTime quarterDate = DateTime.Now.AddYears(-4); quarterDate <= DateTime.Now; quarterDate = quarterDate.AddMonths(3))
{
yield return quarterDate;
}
}
public static void RunSnippet()
{
var RMAs = new[] {
new { Problem = "P", CreatedDate = new DateTime(2012, 6, 2) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 7) },
new { Problem = "P", CreatedDate = new DateTime(2011, 12, 8) },
new { Problem = "P", CreatedDate = new DateTime(2011, 8, 1) },
new { Problem = "P", CreatedDate = new DateTime(2011, 4, 1) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 11, 11) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 6, 6) },
new { Problem = "Q", CreatedDate = new DateTime(2011, 3, 3) }
};
var quarters = GetQuarterDates().Select(quarterDate => new { Year = quarterDate.Year, Quarter = Math.Ceiling(quarterDate.Month / 3.0) });
var rmaProblemQuarters = from rma in RMAs
where rma.CreatedDate > DateTime.Now.AddYears(-4)
group rma by rma.Problem into rmaProblems
select new {
Problem = rmaProblems.Key,
Quarters = (from quarter in quarters
join rmaProblem in rmaProblems on quarter equals new { Year = rmaProblem.CreatedDate.Year, Quarter = Math.Ceiling(rmaProblem.CreatedDate.Month / 3.0) } into joinedQuarters
from joinedQuarter in joinedQuarters.DefaultIfEmpty()
select new {
Year = quarter.Year,
Quarter = quarter.Quarter,
Count = joinedQuarters.Count()
})
};
string json = System.Web.Helpers.Json.Encode(rmaProblemQuarters);
Console.WriteLine(json);
}
Which yields:
[{"Problem":"P","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":0},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":1},{"Year":2011,"Quarter":4,"Count":2},{"Year":2011,"Quarter":4,"Count":2},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":1}]},{"Problem":"Q","Quarters":[{"Year":2008,"Quarter":2,"Count":0},{"Year":2008,"Quarter":3,"Count":0},{"Year":2008,"Quarter":4,"Count":0},{"Year":2009,"Quarter":1,"Count":0},{"Year":2009,"Quarter":2,"Count":0},{"Year":2009,"Quarter":3,"Count":0},{"Year":2009,"Quarter":4,"Count":0},{"Year":2010,"Quarter":1,"Count":0},{"Year":2010,"Quarter":2,"Count":0},{"Year":2010,"Quarter":3,"Count":0},{"Year":2010,"Quarter":4,"Count":0},{"Year":2011,"Quarter":1,"Count":1},{"Year":2011,"Quarter":2,"Count":1},{"Year":2011,"Quarter":3,"Count":0},{"Year":2011,"Quarter":4,"Count":1},{"Year":2012,"Quarter":1,"Count":0},{"Year":2012,"Quarter":2,"Count":0}]}]
I've got a collection of object which contains data as follows:
FromTime Duration
2010-12-28 24.0000
2010-12-29 24.0000
2010-12-30 24.0000
2010-12-31 22.0000
2011-01-02 1.9167
2011-01-03 24.0000
2011-01-04 24.0000
2011-01-05 24.0000
2011-01-06 24.0000
2011-01-07 22.0000
2011-01-09 1.9167
2011-01-10 24.0000
In the "FromTime" column, there are data "gaps" i.e. 2011-01-01 and 2011-01-08 are "missing". So what I'd like to do is to loop through a range of dates (in this instance 2010-12-28 to 2011-01-10) and "fill in" the "missing" data with a duration of 0.
As I've just started with LINQ, I feel that it should be "fairly" easy but I can't quite get it right. I'm reading the book "LINQ in Action" but feel that I'm still quite a way off before I can resolve this particular issue. So any help would be much appreciated.
David
I'll define class like bellow:
public class DurDate
{
public DateTime date = DateTime.ToDay;
public decimal dure = 0;
}
and will wrote function like bellow:
private IEnumerable<DurDate> GetAllDates(IEnumerable<DurDate> lstDur)
{
var min = lstDur.Min(x => x.date).Date;
var max = lstDur.Max(x => x.date).Date;
var nonexistenceDates = Enumerable.Range(0, (int) max.Subtract(min).TotalDays)
.Where(x =>!lstDur.Any(p => p.date.Date == min.Date.AddDays(x)))
.Select(p => new DurDate {date = min.Date.AddDays(p), dure = 0});
return lstDur.Concat(nonexistenceDates).OrderBy(x=>x.date);
}
Sample test case:
List<DurDate> lstDur = new List<DurDate> { new DurDate { date = DateTime.Today, dure = 10 }, new DurDate { date = DateTime.Today.AddDays(-5), dure = 12 } };
Edit: It works simply, first I'll going to find min and max range:
var min = lstDur.Min(x => x.date).Date;
var max = lstDur.Max(x => x.date).Date;
What are the days not in the given range:
Where(x =>!lstDur.Any(p => p.date.Date == min.Date.AddDays(x)))
After finding this days, I'll going to select them:
Select(p => new DurDate {date = min.Date.AddDays(p), dure = 0})
At last concatenate the initial values to this list (and sort them):
lstDur.Concat(nonexistenceDates).OrderBy(x=>x.date);
Something like that. I didn't tested it, but I believe, that you will got the idea:
var data = new[]
{
new { Date = DateTime.Now.AddDays(-5), Duration = 3.56 },
new { Date = DateTime.Now.AddDays(-3), Duration = 3.436 },
new { Date = DateTime.Now.AddDays(-1), Duration = 1.56 },
};
Func<DateTime, DateTime, IEnumerable<DateTime>> range = (DateTime from, DateTime to) =>
{
List<DateTime> dates = new List<DateTime>();
from = from.Date;
to = to.Date;
while (from <= to)
{
dates.Add(from);
from = from.AddDays(1);
}
return dates;
};
var result = range(data.Min(e => e.Date.Date), data.Max(e => e.Date.Date))
.Join(data, e => e.Date.Date, e => e.Date, (d, x) => new {
Date = d,
Duration = x == null
? 0.0
: x.Duration
});
Also it would be better to replace this range lambda with some static method.