Linq - Query compare to record before - c#

This query looks at the record before it and subtracts the times to calculate the total amount of time. However, when the first record is evaluated it throws an error on the third line after the subtraction. Index out of bounds. When I remove the Sum() then the error goes away, but I need the sum efficiently.
var allRecorded = queryable.Where(x => x.DataId== Id);
var dataFiltered = allRecorded.Where(x => x.DataValue >= lowerThreshold && x.DataValue < upperThreshold);
var sumOfExactTimes = dataFiltered.Select(times => (times.EndTime - allRecorded.Where(time2 => time2.EndTime < times.EndTime).Select(x => (DateTime?) x.EndTime).Max()).GetValueOrDefault().TotalMinutes).Sum();
Is there anything else I'm missing?

The problem with the query you have above is that when you reach the item with the minimum EndTime, nothing is found giving you an empty result. You then try to take the maximum of an empty collection which causes the error.
However this query could be simplified tremendously. It would be easier to sort it first then aggregate to find the differences.
var data = queryable
.Where(item => item.DataId == id
&& item.DataValue >= lowerThreshold
&& item.DataValue < upperThreshold)
.OrderBy(item => item.EndTime)
.ToList();
var sumOfExactTimes = data.Skip(1)
.Aggregate(
Tuple.Create(data.First(), 0.0), // [1] Prev Item [2] Result
(seed, item) =>
Tuple.Create(
item,
seed.Item2 + (item.EndTime - seed.Item1.EndTime).TotalMinutes),
result => result.Item2);

Related

How to sum a field on linq that has a where clause which pulls data that returns nulls too?

I have a LINQ query, which groups by the company's Warehouse descriptions. Now, I had to sum the items' weight for the month. But then I have another field, which sums the total weight of scrap, which is found by a field that that contains the word "SCRP". I get the infamous object reference error is not set to an instance of an object error when I do this. I assume it's because there are nulls or something. The field contains other values or nulls.
This is the query that I have. It runs perfectly until I try to add the UserSequence bit:
P.s the scrap percentage doesn't work either, but I assume this is because of the same issue.
var fistDayofPreviousMonth = DateTime.Today.AddMonths(-4);
var testScrapQuery = from s in mapicsSession.Query<InventoryHistory>()
where s.PostedTimestamp > fistDayofPreviousMonth
orderby s.Warehouse.Description
group s by s.Warehouse.Description
into test
let tw = mapicsSession.Query<InventoryHistory>()
.Where(x => x.Warehouse.Description == test.Key)
.Sum(x => x.Item.Weight)
let sw = mapicsSession.Query<InventoryHistory>()
.Where(x => x.Warehouse.Description == test.Key
&& x.UserSequence == "SCRP")
.Sum(x => x.Item.Weight)
select new
{
Warehouse = test.Key,
TotalWeight = tw,
ScrapWeight = sw
//ScrapPercentage = (sw / tw) * 100
};
You can fix the first issue by coalescing the value (now it takes 0 as a value if x or x.Item is null):
.Sum(x => x?.Item?.Weight ?? 0)
Or with an additional Where:
.Where(x => x != null && x.Item != null)
.Sum(x => x.Item.Weight)
And I guess this could do for your percentage calculation (prevent division by 0):
ScrapPercentage = tw == 0 ? 100 : (sw / tw) * 100

How to get this linq query to access info from the previous row

I'm new to linq and I'm trying to figure out how to change this linq query so that where it says prevPrice I'm accessing the Price from the previous row
var items = Data.Where(i => i.Date <= startingDate)
.Take(days + 1)
.Average(i => (i.Price - i.prevPrice) * i.Volume);
You can use aggregate function to collect all calculations and than get an average from it
var results = new List<int>();
data.Where(i => i.Date <= startingDate).Take(days)
.Aggregate((a, b) =>
{
results.Add((b.price - a.price)*a.Volume);
return b;
});
var result = results.Average();
This might work. I whipped it up real quick so check for consistency. I will edit when I have a moment:
var items = Data.Where(i => i.Date <= startingDate)
.Take(days + 1)
.Average(i => (
i.Price - Data.Where(p => p.Date < i.Date)
.OrderByDescending(p => p.Date)
.FirstOrDefault(p => o.Price)
) * i.Volume);
Using MoreLinq's Pairwise function, extra variables can be avoided:
var items = Data.Where(i => i.Date <= startingDate)
.Take(days + 1)
.Pairwise( ( previous, current ) => ( current.Price - previous.Price ) * current.Volume )
.Average();
Pairwise provides the predecessor and current element as first and second arguments respectively to the resultSelector function, except for the first element which is only provided as a predecessor to the second element.
Just use the overloaded Select() call, like this:
var whatYouNeed = Data.Where(i => i.Date <= startingDate)
.Take(days + 1)
.ToList(); // I'm too lazy to be fancy, so I'm gonna preempt the LINQ here.
var items = whatYouNeed
.Select((item, index) =>
new
{
Current = item,
Previous = whatYouNeed[(index == 0 ? 0 : index - 1)]
})
.Average(item => (item.Current.Price - item.Previous.Price) * i.Volume);
Caveat: I'm making it so that if the first element is, literally, the first item in the collection, then it will put the first element (again) as Previous. If you don't like that, then modify the ternary to do what you want it to do (or leave me a comment for a sample).
Hope this helps!
A little different approach. Linq is pretty much a simpler foreach so you can use self-incrementing notation to get the element before or after.
int i = Data.Count();
int total = Data.Count();
Data = Data.OrderBy(o => o.Date);
var items = Data
.Where(o => o.Date <= startingDate)
.Take(days + 1)
.Average(o => (o.Price - (i == total ? (0 * (i--)) : Data.ElementAt(total - (i--)).Price)) * o.Volume);

Grouping records using LINQ method syntax

I'm trying to find a way to group records by date (not taking into account time) using the LINQ method syntax but only select one instance of each record (which is ItemId within the model)
My simple query is as follows:
range1.Count(x => ((x.OpenedDate >= todayFirst) && (x.OpenedDate <= todayLast))
How could I count the unique records within this range by ItemId?
Sounds like you want:
var query = range1.Where(x.OpenedDate >= todayFirst && x.OpenedDate <= todayLast)
.GroupBy(x => x.ItemId)
.Select(g => new { ItemId = g.Key, Count = g.Count() });
foreach (var result in query)
{
Console.WriteLine("{0} - {1}", result.ItemId, result.Count);
}
It's possible that I haven't really understood you properly though - it's not clear whether you really want to group by date or item ID
EDIT: If you just want the count of distinct item IDs in that range, you can use:
var count = range1.Where(x.OpenedDate >= todayFirst && x.OpenedDate <= todayLast)
.Select(x => x.ItemId)
.Distinct()
.Count();

Combining two simple related linq queries

I have two queries and i'm using the result of the first one in the second one like this
var temp = (ObjectTable.Where(o => o.Category == "Y"));
var anonymousObjList = temp.Select(o => new {o, IsMax = (o.Value == temp.Max(x => x.Value))});
Is there a way to combine these into one query?
EDIT:
I cannot just chain them directly because I'm using temp.Max() in the second query.
Why? it would be clearer (and more efficient) to make it three:
var temp = (ObjectTable.Where(o => o.Category == "Y"));
int max = temp.Max(x => x.Value);
var anonymousObjList = temp.Select(o => new {o, IsMax = (o.Value == max)});
You can do it in one statement using query syntax, using the let keyword. It only evaluates the 'max' once, so it just like the three separate statements, just in one line.
var anonymousObjList = from o in ObjectTable
where o.Category == "Y"
let max = ObjectTable.Max(m => m.Value)
select new { o, IsMax = (o.Value == max) };
This is the only time I ever use query syntax. You can't do this using method syntax!
edit: ReSharper suggests
var anonymousObjList = ObjectTable.Where(o => o.Category == "Y")
.Select(o => new {o, max = ObjectTable.Max(m => m.Value)})
.Select(#t => new {#t.o, IsMax = (#t.o.Value == #t.max)});
however this is not optimal. The first Select is projecting a max Property for each item in ObjectTable - the Max function will be evaluated for every item. If you use query syntax it's only evaluated once.
Again, you can only do this with query syntax. I'm not fan of query syntax but this makes it worthwhile, and is the only case in which I use it. ReSharper is wrong.
Possibly the most straightfirward refactoring is to replace all instances of "temp" with the value of temp. Since it appears that this value is immutable, the refactoring should be valid (yet ugly):
var anonymousObjList = ObjectTable.Where(o => o.Category == "Y")
.Select(o => new {o, IsMax = (o.Value == ObjectTable.Where(o => o.Category == "Y").Max(x => x.Value))});
As has already been pointed out, this query really has no advantages over the original, since queries use deffered execution and can be built up. I would actually suggest splitting the query even more:
var temp = (ObjectTable.Where(o => o.Category == "Y"));
var maxValue = temp.Max(x => x.Value);
var anonymousObjList = temp.Select(o => new {o, IsMax = (o.Value == maxValue)});
This is better than the original because every time "Max" is called causes another iteration over the entire dataset. Since it is being called in the Select of the original, Max was being called n times. That makes the original O(n^2)!

Linq skip is skipping the wrong results?

I'm using a linq statement to get my company reviews, but it's skipping the wrong results.
If i pass it currentPage = 1 it gets the right results, if i pass it currentPage 2 it does it wrong. The amount of reviews per page is 10. The GUID id is also passed on correctly.
public IList<SupplierReview> GetAmount(int pageAmount, int currentPage, Guid companyId)
{
var total = _context.SupplierReview.Count();
var skipAmount = pageAmount * (currentPage - 1);
if (skipAmount < total)
{
if (pageAmount < (total - skipAmount))
{
//return the page amount of reviews
return _context.SupplierReview
.Where(x => x.Company.Id == companyId)
.OrderBy(x => x.Id)
.OrderBy(x => x.ReviewDate)
.Skip(skipAmount)
.Take(pageAmount)
.ToList();
}
else
{
//if the left amount is less than the pageAmount,
//then only the leftovers should get taken
return _context.SupplierReview
.Where(x => x.Company.Id == companyId)
.OrderBy(x => x.Id)
.OrderBy(x => x.ReviewDate)
.Skip(skipAmount)
.Take(total - skipAmount)
.ToList();
}
}
//if nothings left, return null
return null;
}
The mistake is probably silly, but I can't seem to see it... Any ideas?
Thanks!
My first thought about skipping too many pages was incorrect. It looks like you are mixing up the total count:
var total = _context.SupplierReview.Count();
then the count which match your company.ID:
Where(x => x.Company.Id == companyId)
I feel like the total calculation should include this Where? Look at your else condition. If you .Take(total - skipAmount), and total was calculated without regard to your company ID, you will end up trying to take too many, potentially a non-existent amount.
It is fine to specify bigger number than (actual)collection count to both Skip & Take extension methods, in this case you get an empty collection.
That means we can refine the code as shown below.
return _context.SupplierReview
.Where(x => x.Company.Id == companyId)
.OrderBy(x => x.Id)
.OrderBy(x => x.ReviewDate)
.Skip(pageAmount * currentPage) //use currentPage + 1 if index assumed to start with 1
.Take(pageAmount)
.ToList();

Categories

Resources