LINQ how get max rating - linq

I have a model with the following entities:
Game: Id
User: Id
UserRating: Id, UserId, GameId, Value
Users can give a rating for a game.
I want to have a query that returns the top 5 games with the highest rating. The highest rating should be based on the Value, but when there are 2 or more games with the same rating, the count should also be taken into account.
How do express this in a query, return the result list as Game entities, using lambda expressions ?
(using EF 4, MVC, SQL Server).

I assume that by "highest rating" you mean "highest average rating"? It sounds like you want something like:
var query = from rating in db.UserRating
group rating by rating.GameId into ratings
orderby ratings.Average(x => x.Value) descending,
ratings.Count() descending
join game in db.Game on ratings.Key equals game.Id
select game;
var top5Games = query.Take(5);
EDIT: In non-query-expression form, this would be more painful, though still feasible:
var top5Games = db.UserRating
.GroupBy(rating => rating.GameId)
.OrderByDescending(ratings => ratings.Average(x => x.Value))
.ThenByDescending(ratings => ratings.Count())
.Join(db.Game, r => r.Key, g => g.Id, (r, g) => g)
.Take(5);
In this case doing it in the lambda syntax isn't too bad, as you're only getting the game out of the join... but more generally, it becomes nastier. It's definitely worth understanding and using both forms, depending on which is the simpler approach for the query at hand. In particular, when you start doing multiple joins it becomes horrible in lambda syntax.
Note that this won't give the rating for that game... to get that, you'd probably want something like:
select new { Game = game,
RatingAverage = ratings.Average(x => x.Value),
RatingCount = ratings.Count() }
at the end of the query instead of just select game.
Also note that you may want to exclude games which currently don't have enough ratings to be meaningful yet - otherwise a game with a single rating which is "perfect" will always be at the top.
EDIT: Okay, with the final tie-break of "name of game", I'd definitely use the query expression form:
var query = from rating in db.UserRating
join game in db.Game on ratings.Key equals game.Id
select new { rating, game } into pair
group pair by pair.game into pairs
orderby pairs.Average(x => x.rating.Value) descending,
pairs.Count() descending,
pairs.Key.Name
select pairs.Key;
var top5Games = query.Take(5);

Related

How to write SQL translateable linq code that groups by one property and returns distinct list

I want to change code below to be sql translateable because now i get exception.
Basicallly i want list of customers from certain localisation and there could be more than one customer with the same CustomerNumber so i want to take the one that was most recently added.
In other words - distinct list of customers from localisation where "distinct algorithm" works by taking the most recently added customer if there is conflict.
The code below works only if it is client side. I could move Group By and Select after ToListAsync but i want to avoid taking unnecessary data from database (there is include which includes list that is pretty big for every customer).
var someData = await DbContext.Set<Customer>()
.Where(o => o.Metadata.Localisation == localisation)
.Include(nameof(Customer.SomeLongList))
.GroupBy(x => x.CustomerNumber)
.Select(gr => gr.OrderByDescending(x => x.Metadata.DateAdded).FirstOrDefault())
.ToListAsync();
Short answer:
No way. GroupBy has limitation: after grouping only Key and Aggregation result can be selected. And you are trying to select SomeLongList and full entity Customer.
Best answer:
It can be done by the SQL and ROW_NUMBER Window function but without SomeLongList
Workaround:
It is because it is not effective
var groupingQuery =
from c in DbContext.Set<Customer>()
group c by new { c.CustomerNumber } into g
select new
{
g.Key.CustomerNumber,
DateAdded = g.Max(x => x.DateAdded)
};
var query =
from c in DbContext.Set<Customer>().Include(x => x.SomeLongList)
join g in groupingQuery on new { c.CustomerNumber, c.DateAdded } equals
new { g.CustomerNumber, g.DateAdded }
select c;
var result = await query.ToListAsync();

How to get top 10 customers based on values of orders

I have a list of Customers who each have a list of Orders. Each Order has a list of LineItems.
I would like to write a LINQ query that would get me the top 10 customers based on order value (i.e. money spent) and not the total number of orders.
One customer could have 2 orders but could have spent £10,000, but another customer could have 100 orders, and only spent £500.
Right now, I have this which gets me the top 10 customers by the number of orders.
var customers = (from c in _context.Customers where c.SaleOrders.Count > 0
let activeCount = c.SaleOrders.Count(so => so.Status != SaleOrderStatus.Cancelled)
orderby activeCount descending
select c).Take(10);
UPDATE
Thanks to Jon Skeet's comment about doing a double Sum, I wrote the following query which compiles.
var customers = (from c in _context.Customers where c.SaleOrders.Count > 0
let orderSum = c.SaleOrders.Where(so => so.Status != SaleOrderStatus.Cancelled)
.Sum(so => so.LineItems.Sum(li => li.CalculateTotal()))
orderby orderSum descending
select c).Take(10);
But when I run this, I get the following error:
It seems LINQ doesn't recognise my .CalculateTotal() method which sit on my LineItem.cs entity.
The problem you were seeing is that CalculateTotal() is not something that Linq can translate into SQL (which is done at run-time, hence no complier error).
The essential problem here is that Linq doesn't really work on lambdas (Func<>), but actually Expressions (Expression<Func<>>), which is the code in a partial compiled state, which Linq then goes about disassembling and translating into SQL.
So, let assume CalculateTotal is a member function defined like this:
public decimal CalculateTotal()
{
return this.quantity * this.value;
}
We could define that as a local lambda function
Func<LineItem, decimal> CalculateTotal = (li => li.quantity * li.value);
Now, we have a lambda which takes a LineItem and returns a value, which is exactly what Sum() wants, so now we can replace:
.Sum(so => so.LineItems.Sum(li => li.CalculateTotal()))
with
.Sum(so => so.LineItems.Sum(CalculateTotal))
But that will crash, just as it did before, because, as I said, it wants an Expression. So, we give it one:
Expression<Func<LineItem, decimal>> CalculateTotal = (li => li.quantity * li.value);

Linq: Count number of times a sub list appear in another list

I guess there must be an easy way, but not finding it. I would like to check whether a list of items, appear (completely or partially) in another list.
For example: Let's say I have people in a department as List 1. Then I have a list of sports with a list of participants in that sport.
Now I want to count, in how many sports does all the people of a department appear.
(I know some tables might not make sense when looking at it from a normalisation angle, but it is easier this way than to try and explain my real tables)
So I have something like this:
var peopleInDepartment = from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList()
};
var peopleInTeam = from s in Sports
select new
{
SportID = s.SportID,
PeopleInSport = s.Participants.Select(x => x.PersonID),
NoOfMatches = peopleInDepartment.Contains(s.Participants.Select(x => x.PersonID)).Count()
};
The error here is that peopleInDepartment does not contain a definition for 'Contains'. Think I'm just in need of a new angle to look at this.
As the end result I would like print:
Department 1 : The Department participates in 3 sports
Department 2 : The Department participates in 0 sports
etc.
Judging from the expected result, you should base the query on Department table like the first query. Maybe just include the sports count in the first query like so :
var peopleInDepartment =
from d in Department_Members
group d by r.DepartmentID into g
select new
{
DepartmentID = g.Key,
TeamMembers = g.Select(r => d.PersonID).ToList(),
NumberOfSports = Sports.Count(s => s.Participants
.Any(p => g.Select(r => r.PersonID)
.Contains(p.PersonID)
)
)
};
NumberOfSports should contains count of sports, where any of its participant is listed as member of current department (g.Select(r => r.PersonID).Contains(p.PersonID))).

LINQ - How to select and count the right results

I have 3 tables: DiaryPosts, DiaryImpressions, Impressions.
Impressions is a small list with some fields: 'like', 'dislike', 'agree', 'disagree'. The user can vote according to what he thinks about the post.
DiaryImpressions is the table that handles the relationship between the posts and the users who vote.
My problem is, I have to count results of each impression vote for each post, so maybe for one post 13 users voted for like, 2 for dislike, 34 agree and 1 disagree.
I have no idea how to perform the query in some way I can get this specific count results for each impression.
Can anyone help me with that?
You can do this via the GroupBy method. It allows you to automatically "group" the elements based on the impression, and count the results per group.
var post(from c in posts selec c)
string post1like;
string post2dislike;
string postagree3;
string postdisagree3;
foreach (var item in posts)
{
var ipressionslike=(from c in imressions where impressioid=item.id where c.impressionID='LIKE' select c.userID).Tolist()
var ipressionsdislike=(from c in imressions where impressioid=item.id where c.impressionID='disLIKE' select c.userID).Tolist()
var ipressionslike=(from c in imressions where impressioid=item.id where c.impressionID='LIKE' select c.userID).Tolist()
var ipressionsagree=(from c in imressions where impressioid=item.id where c.impressionID='disLIKE' select c.userID).Tolist()
post1like+=item.id+""+ipressionsdislike.count;
post2dislike+=item.id+""+ipressionslike.count;
postagree3+=item.id+""+ipressionsagree.count;
}
it should count the impressions for each post and count the amount of users that like or dislike so if you have 30 people dislike for each post it should get them I cannot see your table structure but I hope it will help or point you somewhere
I have to guess but here is what I think the SQL would look like:
SELECT I.Name, COUNT(I.Name)
FROM DairyPosts P
JOIN DairyImpressions DI ON P.ID=DI.DairyID
JOIN Impressions I ON DI.ImpressionsID = I.ID
And what the linq would look like:
List<Dairy> dairyposts = GetDairyPosts();
var impressionCounts =
from p in dairyposts
group p by p.ImpressionName into g
select new { Type = g.Key, ImpressionCount = g.Count() };
Of course I have to assume your list is created a certain way, if you can post how your list is actually created that would help. (As I said in the comments)

LINQ count in group from join

I have a LINQ statement I am trying to get right, so maybe going about this all wrong. My objective is to query a table and join in another table to get counts.
Places
ID, Display
ProfilePlaces
ID, PlaceID, Talk, Hear
Basically Places have ProfilePlaces in a one to many relationship. I want to get the number SUM of ProfilePlaces that have Talkand Hear. Talkand Hear are bit fields.
The following gives me a unique list of Places, so I need to add in the Talkand Hear counts.
var counts = from p in db.Places
join pp in db.ProfilePlaces on p.ID equals pp.PlaceID
group new { Place = p } by p.Display;
I thought something like this, but not having any luck
var counts = from p in db.Places
join pp in db.ProfilePlaces on p.ID equals pp.PlaceID
group new { Place = p,
Talk = pp.Count(t => t.Talk == true),
Hear = pp.Count(t => t.Hear == true)
} by p.Display;
Thanks for any help.
You want to do a GROUP JOIN to get the counts for each Place.
var counts2 = from p in places
join pp in profilePlaces on p.ID equals pp.PlaceID into g
select new
{
Place = p,
CountMeet = g.Count(a => a.Meet),
CountTalk = g.Count(a => a.Talk)
};
Here's the documentation on the different joins from MSDN:
http://msdn.microsoft.com/en-us/library/bb311040.aspx

Resources