I am trying to write a LINQ query that gets all the records and groups them by Period i.e. Sep-18 and then returns the record with the highest Version number within the periods. For example if I have three periods contained within my periodNames list the output list should return:
Sep-18
Versions: 1, 2, 3 (Returns record with version 3)
Oct-18
Versions: 1, 2 (Returns record with version 2)
Nov-18
Versions: 1, 2, 3, 4 (Returns record with version 4)
This is the query I have written so far:
var previousStatements = _context.Statements.Where(x => periodNames.Contains(x.Period) &&
x.Version == _context.Statement.Max(y => y.Version)).toList();
How can I adapt this to the above specification? Thanks
You can use GroupBy in order to group the statements and Max in order to find the maximum value, e.g.
var previousStatements = _context.Statements.Where(x => periodNames.Contains(x.Period))
.GroupBy(x => x.Period)
.Select(x => new { Period = x.Key, MaxVersion = x.Max(y => y.Version))
.ToList();
The code above returns the Period and the maximum version number only. If you need the record with the highest version number for each period, you can use this:
var previousStatements = (ctx.Items.Where(x => periodNames.Contains(x.Period))
.GroupBy(x => x.Period)
.ToArray())
.Select(x => x.OrderByDescending(y => y.Version).First())
.ToList();
Please note that the code above first uses a call to ToArray to send the GroupBy-query to the database. From the returned groups, the row with the highest version number for each period is then retrieved in memory.
Try to use GroupBy and then orderbydescending for the max versiĆ³n:
_context.GroupBy(f => f.Period).Select(f=>f.OrderByDescending(r=>r.Version).First()).ToList();
I think you would have known your solution if you would have written a proper requirement
You wrote:
...groups them by Period i.e. Sep-18 and then returns the highest Version number within the periods
Your examples don't return the highest version number but the row with the highest version number, so let's assume that is what you want:
From a sequence of Statements, group these statements into groups of statements with equal Period, and return from every group, the statement with the largest VersionNumber.
You haven't defined what you want if two statements within the same Period have the same VersionNumber. Let's assume you think that this will not occur, so you don't care which one is returned in that case.
So you have sequence of Statements, where every Statement has a Period and a VersionNumber.
Officially you haven't defined the class of Period and VersionNumber, the only thing we know about them is that you have some code that can decide whether two Periods are equal, and you have something where you can decide which VersionNumber is larger.
IEqualityComparer<Period> periodComparer = ...
IComparer<VersionNumber> versionComparer = ...
If Period is similar to a DateTime and VersionNumber is similar to an int, then these comparers are easy, otherwise you'll need to write comparers.
From your requirement the code is simple:
Take all input statements
Make groups of statements with equal Period
From every group of statements with this Period keep only the one with the highest VersionNumber
IEnumerable<Statement> statements = ...
var latestStatementsWithinAPeriod = statements
.GroupBy(statement => statement.Period, // group by same value for Period
(period, statementsWithThisPeriod) =>
// From every group of statements keep only the one with the highest VersionNumber
// = order by VersionNumber and take the first
statementWithThisPeriod
.OrderByDescending(statement => statement.VersionNumber,
versionComparer)
.FirstOrDefault(),
periodComparer);
Once again: if default comparers can be used to decide when two Periods are equal and which VersionNumber is larger, you don't need to add the comparers.
The disadvantage of the SorBy is that the 3rd and 4rd element etc are also sorted, while you only need the first element, which is the one with the largest VersionNumber.
This can be optimized by using the less commonly used Aggregate:
(period, statementsWithThisPeriod) => statementWithThisPeriod.Aggregate(
(newestStatement, nextStatement) =>
(versionComparer.Compare(newestStatement.VersionNumber, nextStatement.VersionNumber) >=0 ) ?
newestStatement :
nextStatement)
This will put the first statement as the newestStatement (= until now this was the one with the highest version number). The 2nd element will be put in nextStatement. both statements will be compared, and if nextStatement has a VersionNumber larger than newestStatement, then nextStatement will be considered to be newer, and thus will replace newestStatement. The end of the Aggregate will return newestStatement
You can try with GroupBy and OrderByDescending and then take first one.
var statements = _context.Statements
.Where(x => periodNames.Contains(x.Period))
.GroupBy(g => g.Period)
.Select(s => s.OrderByDescending(o => o.Version)
.FirstOrDefault()).ToList();
Related
States have cities. I need the state with most cities only if there is no tie. Tie means top 2 states have the same number of cities.
var stateWithMostCities = _states
.OrderByDescending(_p => _p.cities.Count())
.Take(2)
.ToList();
Now I can check if the city count of first state = second state and determine if there is a tie. However iam asking if this can achieved on the same line shown above using takewhile, skip and other creative uses of linq. Thanks
Something like this?
var stateWithMostCitiesWithoutATie =_states.GroupBy(_p => _p.cities.Count())
.OrderByDescending(g=>g.Key)
.FirstOrDefault(g=> g.Count()==1? g.First():null);
The key is, as #Mong Zhu pointed out to Group by the counts of cities, after that you can order by desc to get the max, and if the max group has more than one then you have a tie
Technically, you can use Aggregate over ordered states:
// the state with maximum cities; null in case of tie
var stateWithMostCities = _states
.OrderByDescending(state => state.cities.Count())
.Take(2) // at most 2 items to analyze
.Aggregate((s, a) => s.cities.Count() == a.cities.Count() ? null : s);
But I doubt if you should do this: comparing top 2 states is more readable.
I am trying to get the first 6 items of offerList that have Min RegularPrice value and whose OfferCode contains "dtv". I tried the following LINQ but it retrieves only one item instead of 6. What am I doing wrong?
List<Offer> dtvOffers = offerList.Where(x =>
(x.ListPrice.CommodityPrice.RegularPrice == offerList.Min(y =>
y.ListPrice.CommodityPrice.RegularPrice)) &&
(x.OfferCode.ToLower().Contains("dtv")))
.Take(6).ToList();
Order by RegularPrice and take the first 6 rows.
offerList.Where(x => x.OfferCode.ToLower().Contains("dtv"))
.OrderBy(x.ListPrice.CommodityPrice.RegularPrice)
.Take(6)
.ToList();
This will give you the first six records with the lowest price.
The only plausible explanation to this is that there are not 6 items which remain after your filter.
The Take will take 6 if there are 6 or more items after filter. If not it take what's left. Can also return a blank collection if none left.
Oh and BTW, calculate this line before hand. No use, evaluating for each and every iteration.
var min = offerList.Min(y => y.ListPrice.CommodityPrice.RegularPrice);
List<Offer> dtvOffers = offerList.Where(x =>
(x.ListPrice.CommodityPrice.RegularPrice == min) &&
(x.OfferCode.ToLower().Contains("dtv")))
.Take(6).ToList();
i'm trying to get data from a db, but I don't get the expected result.
var totalNews = GetNewsData(false, CmsPagesRepository.CurrentUserSettings.CmsLanguageID)
.OrderBy(n => n.Priority)
.ThenBy(n => n.Index)
.ThenBy(n => n.Title);
I have a table of News with a column Index and a column Priority, and I want to order the news by Priority and if the Priority is null first show the ones with priority and after the others.
But now if a have 3 news with index (1,4,2) and priority(null,0,1) in the list of totalNews I get on the first position the one with Priority null and index 1. What do I have to correct?
Though the answer you have accepted will work, I don't much like it. First, in the unlikely event that you have some of the largest integer in there, they will not be ordered correctly with respect to null. A good solution works for any inputs, not just common inputs. Second, the code does not match the specification. Your specification is "order first by whether the priority is null, then by priority, then by...", so that's how the code should read. I would suggest you write:
GetNewsData(...)
.OrderBy(n => n.Priority == null) // nulls last
.ThenBy(n => n.Priority)
.ThenBy(n => n.Index)
.ThenBy(n => n.Title);
You probably want a simple null check in the OrderBy priority, like this:
.OrderBy(n => n.Priority ?? int.MaxValue)
This will default the priority to a high number if it is null.
I have an Order class, it consists of a number of properties, one of them being Course.
The Course object contains a list of MeetingDays.
Each MeetingDay object contains numerous properties, one of which is StartDate.
Now I want to sort (OrderBy) a list of orders, ordering it by the StartDate property of a MeetingDay.
Since an order can have several MeetingDays: I also have a date, and I only want to sort by the MeetingDay per order that is equal to the date parameter.
So if one order starts at 10 am and ends at 2 pm I want it ordered in my list before another order that starts at 3 pm and ends at 6 pm.
Edit
It would be nice if something like this was possible:
var sortedOrders = orders.OrderBy(x => x.Course.MeetingDays.StartDate.Date == date.Date).ToList();
Since (according to the comments) you are guaranteed, that each order contains a MeetingDay that matches your given day, the following expression will accomplish what you want:
var sortedOrders = myOrders
.OrderBy(order => order
.Course
.MeetingDays
.Single(day => day.StartDate.Date == date.Date)
.StartDate)
.ToList();
Should, unexpectedly, there be zero or more than one matching MeetingDay, an exception will be thrown at the call to Single.
This will only work if exactly one MeetingDay.StartDate match date:
var sortedOrders = orders.OrderBy(x => x.Course.MeetingDays.Single(y => y.StartDate.Date == date.Date).StartDate).ToList();
I have this query that gives the correct results but it takes about 15 seconds to run
int Count= P.Pets.Where(c => !P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName).Contains(c.OwnerName) && c.CreatedDate >=
EntityFunctions.AddDays(DateTime.Now, -8)).GroupBy(b=>b.OwnerName).Count();
If I remove this part of the linq
'&& c.CreatedDate >= EntityFunctions.AddHours(DateTime.Now, -8)'
It only takes about 3 seconds to run. How can I keep the same condition happening but a lot faster?
I need that date criteria because I don't want any Classeses that were created 8 days old to be included in the count
Edit
I have a table by the name of People which is referred to in this query as P and I want to return a count of the total of Pets they are that do not have a owner and remove the ones from the query that don't do have an owner even if they exist in another Pet reference has not the owner of that Pet. Meaning if a person has at least one record in the Pets table to be considered as an owner of a pet than I want to remove all cases where that person exist in the return query and once that is done only return the Pets that have been created newer than 8 days
You should cache the date and put that evaluation first (since the DateTime evaluation should be faster than a Contains evaluation). Also avoid recalculating the same query multiple times.
DateTime eightDaysOld = EntityFunctions.AddHours(DateTime.Now, -8);
//calculate these independently from the rest of the query
var ownedPetOwnerNames = P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName);
//Evaluate the dates first, it should be
//faster than Contains()
int Count = P.Pets.Where(c => c.CreatedDate >= eightDaysOld &&
//Using the cached result should speed this up
ownedPetOwnerNames.Contains(c.OwnerName))
.GroupBy(b=>b.OwnerName).Count();
That should return the same results. (I hope)
You are loosing any ability to use indices with that snippet, as it calculates that static date for every row. Declare a DateTime variable before your query and set it to DateTime.Now.AddHours(-8) and use the variable instead of your snippet in the where clause.
By separating the query and calling ToList() on it and inserting it in the master query make it go 4 times faster
var ownedPetOwnerNames = P.Pets.Where(a => a.IsOwned == true)
.Select(a => a.OwnerName).ToList();
int Count = P.Pets.Where(c => c.CreatedDate >= Date&&
ownedPetOwnerNames.Contains(c.OwnerName)).GroupBy(b=>b.OwnerName).Count();
You could use (and maybe first create) a navigation property Pet.Owner:
var refDate = DateTime.Today.AddDays(-8);
int Count= P.Pets
.Where(p => !p.Owner.Pets.Any(p1 => p1.IsOwned)
&& p.CreatedDate >= refDate)
.GroupBy(b => b.OwnerName).Count();
This may increase performance because the Contains is gone. At least it is better scalable than your two-phase query with a Contains involving an unpredictable number of strings.
Of course you also need to make sure there is an index on CreatedDate.