I'm trying use the Sum method in a lambda expression for a comparison, but I want to use it for multiple comparisons. How do I accomplish this? I've looked at "Let" and "SelectMany", but I haven't been able to find an answer.
Below is what the code looks like:
return _dbContext.All<Table>()
.Where(table => table.CurrentLevel <= salesCriteria.MaxTableLevel)
.Where(table => table.Leg
.Where(leg=> salesCriteria.StartDate <= leg.AddDate)
.Where(leg=> leg.AddDate <= salesCriteria.EndDate)
.Sum(leg => leg.Width) <= salesCriteria.MaxGoalAmount);
As you can see, I'm trying to get all Tables with certain criteria that have Legs with certain criteria and whose width all add up to be less than a certain value. I would also like to make sure that the Sum is greater than a certain min value. However, I can't do that here since as soon as I do .Sum, I lose the list. So how would I accomplish that here? All I want is minValue <= .Sum() <= maxValue
It sounds like you want something like:
return _dbContext.All<Table>()
.Where(table => table.CurrentLevel <= salesCriteria.MaxTableLevel)
.Select(table => new {
table,
legWidth = table.Leg
.Where(leg=> salesCriteria.StartDate <= leg.AddDate)
.Where(leg=> leg.AddDate <= salesCriteria.EndDate)
.Sum(leg => leg.Width)
})
.Where(x => x.legWidth <= salesCriteria.MaxGoalAmount &&
x.legWidth >= salesCriteria.MinGoalAmount)
.Select(x => x.table);
So the Select here is the equivalent of using a let in a query expression.
As a query expression, this would be:
return from table in _dbContext.All<Table>()
where table.CurrentLevel <= salesCriteria.MaxTableLevel
let legWidth = table.Leg
.Where(leg=> salesCriteria.StartDate <= leg.AddDate)
.Where(leg=> leg.AddDate <= salesCriteria.EndDate)
.Sum(leg => leg.Width)
where legWidth <= salesCriteria.MaxGoalAmount &&
legWidth >= salesCriteria.MinGoalAmount
select table;
To get the power of let, you need to switch from method-chaining syntax to query expression syntax. Try this:
var goodTables =
from table in _dbContext.All<Table>()
where table.CurrentLevel <= salesCriteria.MaxTableLevel
let sumOfWidthOfGoodLegs =
table.Leg
.Where(leg=> salesCriteria.StartDate <= leg.AddDate)
.Where(leg=> leg.AddDate <= salesCriteria.EndDate)
.Sum(leg => leg.Width)
where sumOfWidthOfGoodLegs <= salesCriteria.MaxGoalAmount
// can insert another where on sumOfWidthOfGoodLegs here as required
select table;
return goodTables.ToList();
I note that this is checking the width-sum of only the good legs - I'm not convinced this is what you want, but it's what you're doing at present.
Related
I have this LINQ Where clause that is declaring 2 variables on the SQL query
var parkingLotPrice =
_context.ParkingLotPrice
.Where(x => currentDate >= x.EffectiveDate && (currentDate <= x.ExpiryDate || x.ExpiryDate == null))
.ToQueryString();
It generates this SQL Query:
DECLARE #__currentDate_0 datetime2 = '2021-07-21T17:48:29.1106534-06:00';
DECLARE #__currentDate_1 datetime2 = '2021-07-21T17:48:29.1106534-06:00';
SELECT [p].[ParkingLotId],
[p].[PriceScheduleId],
[p].[EffectiveDate],
[p].[ExpiryDate]
FROM [ParkingLotPrice] AS [p]
WHERE (#__currentDate_0 >= [p].[EffectiveDate]) AND ((#__currentDate_1 <= [p].[ExpiryDate]) OR [p].[ExpiryDate] IS NULL)
Note: the declarations contains the same value.
The problem is the (currentDate <= x.ExpiryDate || x.ExpiryDate == null).
If I remove the null evaluation, it only declares 1 variable.
DECLARE #__currentDate_0 datetime2 = '2021-07-21T17:32:31.3980763-06:00';
SELECT [p].[ParkingLotId],
[p].[PriceScheduleId],
[p].[EffectiveDate],
[p].[ExpiryDate]
FROM [ParkingLotPrice] AS [p]
WHERE (#__currentDate_0 >= [p].[EffectiveDate]) AND (#__currentDate_0 <= [p].[ExpiryDate])
Is there a way of keeping the Where evaluation, but only declare 1 variable?
And what is wrong with it? Does it return a wrong result? I created hundreds queries with null and without and they always returned the right result.
I can see the real problem in your query. The way you compare dates. It will compare times too. In some cases it will return a wrong result. I highly recommend you to compare only dates
var parkingLotPrice = _context.ParkingLotPrice
.Where(x => EF.Functions.DateDiffDay(x.EffectiveDate,currentDate) >=0
&& (x.ExpiryDate == null || EF.Functions.DateDiffDay(currentDate,x.ExpiryDate)>=0 )).ToList();
or if you need some time try this
var parkingLotPrice = _context.ParkingLotPrice
.Where(x => EF.Functions.DateDiffMinute(x.EffectiveDate,currentDate) >=0
&& (x.ExpiryDate == null || EF.Functions.DateDiffMinute(currentDate,x.ExpiryDate)>=0 )).ToList();
var MyCours = Db.COURS.Where(C => C.CLASSE_ID == ClassID
&& DateTime.Now>= C.START_DATE
&& DateTime.Now <= C.END_DATE)
.ToList();
Some change still dont work !
A likely problem is that the provider can't project DateTime.Compare into a SQL statement. There is potentially also a logical error in the direction of comparison (unless you really want enddate < now < startdate), and I would also suggest using .ToList() to materialize into a list:
var theTimeNow = DateTime.Now;
var MyCours = Db.COURS.Where(C => C.CLASSE_ID == ClassID
&& theTimeNow >= C.START_DATE
&& theTimeNow <= C.END_DATE)
.ToList();
Projecting DateTime.Now into a variable isolates the non-determinism of it, i.e. to ensure that both comparisons are against the same time.
All,
I cannot seem to figure this out. I looked here and tried some different lambda expressions but to no avail. I am trying to do this for my condition:
(DateOrganized >= startDate.Date && DateOrganized <= endDate.Date)
This is all I have that will compile. How can I add another condition like above? Thanks
r.Count(x => x.DateOrganized <= endDate.Date);
This does not compile
r.Count(x => x.DateOrganized >= startDate.Date && x => x.DateOrganized <= endDate.Date);
Try
r.Count(x => (x.DateOrganized >= startDate.Date) &&
(x.DateOrganized <= endDate.Date));
I make one database trip to get a list of entities.
I then would like to separate this list into 2 lists, one for the entities that have not expired (using a start and end) which i call TopListings and another which are regular listings, those that have expired or have start/end date as null (the ones that are not TopListings)
I am not entirely sure which filtering is fasted to separate into 2 lists, should I get the toplist first, then filter second list based on what is NOT in the top list for second?
var listings = ListingAdapter.GetMapListings(criteria);
var topListings = listings.Where(x => x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now);
//I AM NOT SURE WHAT THIS LINE SHOULD BE
var regularListings = listings.Where(x => x.TopStartDate < DateTime.Now || x.TopExpireDate < DateTime.Now || x.TopStartDate == null || x.TopExpireDate == null );
Thank you
You might want to use a LookUp
like this:
var lookup = listings.ToLookup(x => x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now);
var topListings = lookup[true];
var regularListings = lookup[false]; // I assume everything not a topListing is a regular listing.
If this isnt enough, you could create an enum
enum ListingType { Top, Regular, WhatEver };
...
var lookup = listings.ToLookUp(determineListingType); // pass a methoddelegate that determines the listingtype for an element.
...
var topListings = lookup[ListingType.Top];
var regularListings = lookup[ListingType.Regular];
var whateverListings = lookup[ListingType.WhatEver];
In this case, it would probably be easier to use a loop, instead of Linq operators:
var topListings = new List<Listing>();
var regularListings = new List<Listing>();
foreach (var x in listings)
{
if (x.TopStartDate >= DateTime.Now && x.TopExpireDate >= DateTime.Now)
topListings.Add(x);
else
regularListings.Add(x);
}
This is also more efficient, because the list is enumerated only once.
Take a look at the 'Except' operator to make things a little easier. You might have to add a .ToList() on topListings first though.
var regularListings = listings.Except(topListings);
http://blogs.msdn.com/b/charlie/archive/2008/07/12/the-linq-set-operators.aspx
Make use of regular foreach loop that's straight forward. You can iterate through listing with one go and add items to appropriate collections. If you are LINQ kind of guy, ForEach extension is what you are looking for:
var topListings = new List<Listing>();
var regularListings = new List<Listing>();
listing.ForEach(item=>{
if (x.TopStartDate < DateTime.Now
|| // I've inverted the condition, since it is faster-one or two conditions will be checked, instead of always two
x.TopExpireDate < DateTime.Now)
regularListings.Add(x);
else
topListings.Add(x);
});
I have the following code:
var allWorkorders =
(from wo in context.WORKORDERs
join wot in context.WORKORDERTYPEs on wo.wot_oi equals wot.wotyoi
join pri in context.PRIORITies on wo.prio_oi equals pri.priooi
join s in context.SITEs on wo.BEparn_oi equals s.siteoi
where wo.audt_created_dttm.Value.Year >= now.Year - 3 && wo.audt_created_dttm.Value.Year >= 2006
&& wo.audt_created_dttm < timeframe && (s.id == "NM" || s.id == "TH") &&
(wo.clsdt_date ?? new DateTime(3000, 01, 01)) < DateTime.Now
group pri by new {s.id, pri.prioid, MonthNum = (wo.clsdt_date ?? new DateTime(3000, 01, 01)).Year * 100 +
(wo.clsdt_date ?? new DateTime(3000, 01, 01)).Month} into groupItem
orderby groupItem.Key.MonthNum, groupItem.Key.id
select new {groupItem.Key.id, groupItem.Key.prioid, groupItem.Key.MonthNum, Unit = groupItem.Count()});
allWorkorders.GroupBy(x => new { x.id, x.MonthNum }).Select(x => new {x.Key.id, x.Key.MonthNum,
Denominator = x.Sum(y => y.Unit), Numerator = x.Where(y => SqlMethods.Like(y.prioid, "1%") ||
SqlMethods.Like(y.prioid, "6%")).Sum(y => y.Unit), Data_Indicator = DATA_INDICATOR,
Budgeted = budgetedPlannedOutageHrs, Industry_Benchmark = INDUSTRY_BENCHMARK,
Comments = comments, Executive_Comments = executiveComments,
Fleet_Exec_Comments = fleetExecComments}).ToList();
I want to create a for loop:
for (int counter = 0; counter < allWorkorders.Count; counter++)
{
var item = allWorkorders[counter];
......
However, I get the following error: " '<' cannot be applied to operands of type 'int' and 'method group'"
So even though I have allWorkorders going to ToList() it's not being recognized as a list.
What am I doing wrong? I have done this in the past, the biggest difference being that in the past cases my ToList was at the end of the select statement.
It is trying to use the LINQ extension method method Count() rather than the List<T>.Count
The reason it is doing this is you are not assigning the results of ToList() to anything. This whole statement is basically ignored because you are not using the return value
allWorkorders.GroupBy(x => new { x.id, x.MonthNum }).Select(x => new {x.Key.id, x.Key.MonthNum,
Denominator = x.Sum(y => y.Unit), Numerator = x.Where(y => SqlMethods.Like(y.prioid, "1%") ||
SqlMethods.Like(y.prioid, "6%")).Sum(y => y.Unit), Data_Indicator = DATA_INDICATOR,
Budgeted = budgetedPlannedOutageHrs, Industry_Benchmark = INDUSTRY_BENCHMARK,
Comments = comments, Executive_Comments = executiveComments,
Fleet_Exec_Comments = fleetExecComments}).ToList();
You didn't assign the second line (the one with ToList()) on it to anything. You ended the assignment of allWorkOrders with: "Unit = groupItem.Count()});"
Dropping on ToList() will make it return a list, but since you didn't assign it to anything it immediately goes out of scope and you lose it.
ToList() returns a result. You need something like
var newList = allWorkorders.GroupBy(x => ...).Select(x => ...).ToList();
You can either use a foreach loop instead or add the () to Count, for (int counter = 0; counter < allWorkorders.Count(); counter++). In this case Count is not a property but a Linq extension method that you're calling on an IEnumerable, which is why it complains about a method group when you give it just .Count instead of calling the method .Count().
Also, you have two separate statements there and don't appear to be storing the part which you are performing the .ToList() against anywhere.