Say I have the following class:
internal class ModuleScrap
{
public System.DateTime ReadTime { get; set; }
public int NetScrap { get; set; }
}
I would like a Linq query that finds for me all NetScrap values that were greater than the NetScrap value before it, based on ReadTime. So, a query that looks something like this:
MyList
.OrderBy(row => row.ReadTime)
.Where (row => row.NetScrap > [The Previous NetScrap Value])
Is such a query possible?
Yes, using Zip and Skip (assuming .NET 4):
// Avoid having to do the ordering twice
var ordered = list.OrderBy(row => row.ReadTime).ToList();
var greater = ordered.Zip(ordered.Skip(1), (x, y) => new { x, y })
.Where(p => p.y.NetScrap > p.x.NetScrap)
.Select(p => p.y);
Zipping a sequence with itself skipped one gives you pairs of consecutive elements:
Original a b c d e f
Original.Skip(1) b c d e f g
If you read each column from the above, you get the pairings. From there, you just need to select each value where the second entry's NetScrap is greater than the first.
Related
I have been trying for days now to solve the following problem in WCF RIA for lightswitch using linq:
the entity is:
moveDate Direction moveQuantity moveSignedQty
moveSignedQty is positive for In and negative for Out Direction
What I need to do is create a WCF Service in the form
tDate OBalance CBalance
CBalance is the sum of moveSignedQty for a particular moveDate
OBalance is the sum of mpveSignedQt for the previous moveDate; it is zero if there in no previous day value.
My approach below did not work:
Dim close = From c In Me.Context.StockMovements
Order By c.DateOfMovement
Group New With {c} By _
tranDate = CDate(c.DateOfMovement) _
Into g = Group
Let cBal = g.Sum(Function(s) s.c.SignedQuantity_KG)
Let fDate = g.OrderBy(Function(d) d.c.DateOfMovement).FirstOrDefault
Select New accStockBalance With {
.TransactionDate = tranDate, _
.ClosingBalance = cBal}
Dim sBal = close.GroupBy(Function(d) d.TransactionDate).Select( _
Function(b)
Dim subb = b.OrderBy(Function(t) t.TransactionDate)
Return subb.Select( _
Function(s, i) New With {
.TransactionDate = s.TransactionDate, _
.ClosingBalance = subb.ElementAt(i).ClosingBalance, _
.OpeningBalance = If(i = 0, 0, subb.ElementAt(i - 1).ClosingBalance)})
End Function)
Example:
moveDate Direction moveQuantity moveSignedQty
13/02/2013 In 30 30
13/02/2013 Out 4 -4
13/02/2013 Out 10 -10
14/02/2013 Out 4 -4
14/02/2013 Out 4 -4
14/02/2013 In 7 7
15/02/2013 In 15 15
Expected result:
tDate OBalance cBalance
13/02/2013 0 16
14/02/2013 16 15
15/02/2013 15 30
The last bit sBal threw up an error that lambda statements cannot be converted to expression trees.
Kindly guide me. I have read several Q and A in this and other forums help please.
Please forgive my terrible formatting couldnt figure out how to format the example to table format
The function below is composed of 2 statements.
Function(b)
Dim subb = ...
Return ...
This kind of function can't be used in LINQ query.
Moreover, the operators ElementAt and Select using index arguments aren't supported by EntityFramework.
I show you here a solution. It is written in C#. I do not think you'll have difficulty translating the code in VB. Furthermore, I do not use the fluent LINQ syntax that I do not find very understandable. Finally, for pedagogical concern I avoid using anonymous types.
The first thing to do is actually write a query that sums the movements by date. This request must allow to obtain a list of object (MovementSumItem class) each containing the date and the sum of the movements of the day.
class MovementSumItem
{
public DateTime Date { get; set; }
public int? TotalQty { get; set; }
}
The TotalQty property is declared as nullable. I'll explain why later.
I do not understand why your close query is so complicated!?! You just need to use the GroupBy operator once. And there is no interest in using the OrderBy operator.
IQueryable<MovementSumItem> movementSumItemsQuery =
context.StockMovements
.GroupBy(
// group key selector: the key is just the date
m => m.MoveDate,
// project each group to a MovementSumItem
(groupKey, items) => new MovementSumItem {
// date is the key of the group
Date = groupKey,
// items group sum
TotalQty = items.Sum(i => i.SignedQty),
});
Now, we must be able to determine for each item which is the previous item. And this of course without first execute the query below.
Here is the logical expression I propose:
Func<MovementSumItem,MovementSumItem> previousItemSelector = item =>
movementSumItemsQuery // from all items
.Where(b => b.Date < item.Date) // consider only those corresponding to a previous date
.OrderByDescending(b => b.Date) // ordering them from newest to oldest
.FirstOrDefault(); // take the first (so, the newest) or null if collection is empty
This expression does not use any index notion. It is therefore compatible with Entity Framework.
Combining this expression with the query above, we can write the complete query. This final request must allow to obtain a list of object (BalanceItem class) each containing the date, the opening balance (sum of movements from previous item) and the closing balance (sum of movements from current item).
class BalanceItem
{
public DateTime Date { get; set; }
public int OpeningBalance { get; set; }
public int ClosingBalance { get; set; }
}
Logically, the final query can be written:
IQueryable<BalanceItem> balanceItemsQuery =
movementSumItemsQuery
.Select(
item => new BalanceItem() {
Date = item.Date,
OpeningBalance = previousItemSelector(item).TotalQty ?? 0,
ClosingBalance = item.TotalQty ?? 0
});
Unfortunately Entity Framework does not support the invocation of the function previousItemSelector. So we must integrate the expression in the query.
IQueryable<BalanceItem> balanceItemsQuery =
movementSumItemsQuery
.Select(
item => new BalanceItem()
{
Date = item.Date,
OpeningBalance = movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault().TotalQty ?? 0,
ClosingBalance = item.TotalQty ?? 0
});
Finally, to run the query, simply use (for example) the ToList operator.
List<BalanceItem> result = balanceItemsQuery.ToList();
Moreover, balanceItemsQuery being IQueryable, you can specify the query by adding, for example, a filter on the date:
IQueryable<BalanceItem> balanceItemsOfTheYearQuery = balanceItemsQuery
.Where(x => x.Date.Year == 2014);
Finally, you can verify that the query executes well via a single SQL query using the ToString function of the Query object.
Console.WriteLine(balanceItemsQuery.ToString());
Why TotalQty is declared nullable?
Otherwise, the OpeningBalance value expression should be written:
OpeningBalance = movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault() == null ? 0 : movementSumItemsQuery
.Where(b => b.Date < item.Date)
.OrderByDescending(b => b.Date)
.FirstOrDefault().TotalQty
However, the comparison .FirstOrDefault() == null is not supported by Entity Framework.
Grouping is an area of LINQ that I haven't quite managed to get my head around yet. I have the following code:
var subTrips = tbTripData
.Where(t => selectedVehicleIds.Contains(t.VehicleID))
.Join(tbSubTripData,
t => t.TripID,
s => s.TripID,
(t, s) => new { t = t, s = s })
.Select(r =>
new SubTrip
{
VehicleID = r.t.VehicleID,
TripID = r.t.TripID,
Sequence = r.s.Sequence,
TripDistance = r.s.TripDistance,
Odometer = r.s.Odometer
})
.ToList();
I'm trying to figure out a LINQ query that will look at subTrips and for each VehicleID, find the first Odometer, i.e. the Odometer corresponding to the lowest TripID and Sequence values.
I've been poking at it for an hour but just can't figure it out. Can anyone offer some advice before I give up and write procedural code to do it?
UPDATE: To clarify, Sequence is the sequential number of each subtrip within a trip. So what I'm looking for is the Odometer from the first subtrip for each vehicle when the subtrips within each grouped VehicleID are ordered by TripID then by Sequence.
I'm not 100% what you're looking for. But this might get you started in the right direction.
var groupedList = (from s in subTrips
group s by s.VehicleID
into grp
select new
{
VehicleID = grp.Key,
Odometer = grp.OrderBy(ex => ex.TripID).Select(ex => ex.Odometer).First(),
TripID = grp.Min(ex => ex.TripID)
}
).ToList();
This will return the VehicleID, the Odometer corresponding to the lowest TripID, and the lowest TripID.
I have 2 set of coolection in memory and i want to return one set based on the 2. My object have the following suructure:
class Item
{
public string key {get,set;}
public int total1 {get;set;}
public int total2 {get ;set;}
}
I would like to "union" them so that when the key on item form set 1 is equal to the key of an item from the set 2 , my union should return an item as follow:
item_union.Key= item1.key==item2.key;
item_union.total1= item1.total1 + item2.total1;
item_union.total2= item1.total2 + item2.total2;
can someone show me how i should build my custom equality compararer to obtain this result?
many thanks in advance
It sounds like you might want a join, or you might just want to concatenate the collections, group by the key and then sum the properties:
// Property names changed to conform with normal naming conventions
var results = collection1.Concat(collection2)
.GroupBy(x => x.key)
.Select(g => new Item {
Key = g.Key,
Total1 = g.Sum(x => x.Total1),
Total2 = g.Sum(x => x.Total2)
});
I have a calculated property
public decimal Avaibility
{
get{ return l => l.Start - l.Uses.Sum(u => u.Amount);}
}
so i can't include this property in any predicate for linq to entities like
Products.Where(l => l.Avaibility> 0);
My current solution is repeat the lambda anywhere i need it but that is not maintainable, i want to reach something like
private Expression AvaibilityCalculation = l => l.Start - l.Uses.Sum(u => u.Amount);
public decimal Avaibility
{
get{ return AvaibilityCalculation.Compile()(this/*product*/) ;}
}
public Product ProductsWithStock()
{
//for l => l.Start - l.Uses.Sum(u => u.Amount) > 0
return Products.Where(AvaibilityCalculation.GreaterThan(Expression.Constant(0, typeof(decimal))) );
}
Any idea?
You could recalculate your calculated value with every save and add it as an extra column to your table. If you are going to query it a lot this won't just make your linq statements easier to use, but it should perform better as well (calculate once).
You could create a method that returns your expression:
public Expression<Func<Product,bool>> AvailablityCompareTo
(bool greaterThan, int amount)
{
if (greaterThan)
return l => l.Start - l.Uses.Sum(u => u.Amount) >= amount;
else
return l => l.Start - l.Uses.Sum(u => u.Amount) <= amount;
}
usage:
Products.Where(AvailablityCompareTo(true,0))
It is a simple solution without expression voodoo, still a bit repetitive, but only inside the method.
I have the following LINQ conditional where clause query that produces a result of weights:
From this, I'd like to take the result set and join on another table, tblPurchases
var result = weights.Join(getsuppliersproducts.tblPurchases,
w => new { w.MemberId, w.MemberName, w.LocationId, w.UnitId },
p => new { p.MemberId, p.MemberName, p.LocationId, p.UnitId },
(w, p) => p);
In this second table, I have two columns I would like to perform an aggreagte function on, a sum on PurchaseQuantity and a count of UnitID.
So in its raw format, tblPurchases would look like so:
MemberID LocationID UnitId SupplierID SupplierStatus Purchases
1 1 ab Sup1 Live 10
1 1 abc Sup1 Live 10
1 1 abcd Sup2 Dead 50
From my results data set, I would like the output to look like so:
MemberID LocationID SupplierID SupplierStatus UnitIdCount Total Purchases
1 1 Sup1 Live 2 50
Also, with these amendments, can I still return this to a List?
How do I implement this using LINQ? I have tried, and failed miserably.
(To those who have seen my previous posts, I'm trying to cover all angles so I can fully understand the concept of what is going on in both SQL and LINQ)
That query will return an IEnumerable where each of the Purchases matches the MemberId, MemberName, LocationId and UnitId in the original Weights query. You can only easily do one aggregate at a time, so
var result = weights.Join(getsuppliersproducts.tblPurchases,
w => new { w.MemberId, w.MemberName, w.LocationId, w.UnitId },
p => new { p.MemberId, p.MemberName, p.LocationId, p.UnitId },
(w, p) => p).ToList();
Int32 count = result.Count();
Double quantity = result.Sum(p => p.PurchaseQuantity);
Is that what you're trying to do?
EDIT, after your reply of I would like to reutrn a list of tblPurchases with two new columns, the sum of Purchase Quantity and count of unit ID.
This gives a flat output:
var query = Weights.GroupJoin(
Purchases,
w => new {w.MemberId, w.LocationId},
p => new {p.MemberId, p.LocationId},
(w,p) => new {w.MemberId, w.LocationId, Count = p.Count(), Sum = p.Sum(x => x.Purchases)} );
Note that at the point we do the (w, p) => new {} that w is a single Weight and p is a list of Purchases matching that weight, so you can still keep all of teh (hierarchical) data:
var query = Weights.GroupJoin(
Purchases,
w => new {w.MemberId, w.LocationId},
p => new {p.MemberId, p.LocationId},
(w,p) => new {w.MemberId, w.LocationId, Count = p.Count(), Sum = p.Sum(x => x.Purchases), Purchases = p} );