Linq Expression that compare Lambda result with a constant - linq

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.

Related

Linq to EF in lightswitch WCF RIA Services

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.

Linq - Retrieve first list after SelectMany

I've an object list where each object contains an internal object list and what I would fetching is the father list (left list), however I'm forced to use SelectMany function..Is it possibile?
Naive Example:
var query = objList.SelectMany(p => p.InternalList)
.Where(internalObj => internalObj.SomeProprerty == true)
.SELECT(objList);
Is there any way to accomplish this?
Assuming you don't actually want objList, but instead the element of objList which we're looking at at the time, I think you just want:
var query = objList.SelectMany(p => p.InternalList, (o, p) => new { o, p })
.Where(pair => pair.p.SomeProperty)
.Select(pair => pair.o);
If that's not what you're after, it would really help if you'd give a concrete example.
EDIT: If you only want any example from objList where any element of the internal list has a SomeProperty value of true, you can do that more easily like this:
var value = objList.FirstOrDefault(o => o.InternalList.Any(p => p.SomeProperty));
if (value != null)
{
...
}

How to convert LINQ nvarchar to double

I'm trying to sort a column, but the value is stored as nvarchar in the database, how can I convert it to a double so it sorts here? I tried to do Convert.ToDouble(t.PressureChange), but it didn't work...
if (column == "PressureChange")
{
if (sortDirection == "ascending")
testResults = testResults.OrderBy(t => t.PressureChange);
else
testResults = testResults.OrderByDescending(t => t.PressureChange);
}
You could try
if (column == "PressureChange")
{
if (sortDirection == "ascending")
{
testResults = testResults.OrderBy(t => double.Parse(t.PressureChange));
}
else
{
testResults = testResults.OrderByDescending
(t => double.Parse(t.PressureChange));
}
}
... but it depends whether that method is supported by LINQ to SQL. To be honest, it sounds like you've got bigger problems in terms of your design: if you're trying to store a double value in the database, you shouldn't be using a varchar field to start with. Fix your schema if you possibly can.
EDIT: Note that based on the information on this page about LINQ to SQL and Convert.* it looks like Convert.ToDouble should work, so please give us more information about what happened when you tried it.
Rather use TryParse to avoid exceptions. In this code I used 0.0 as a default value if it could not parse the string.
double temp = 0.0;
if (column == "PressureChange")
{
if (sortDirection == "ascending")
testResults = testResults.OrderBy(t => (double.TryParse(t.PressureChange.toString(), out temp) ? temp : 0.0)).ToList();
else
testResults = testResults.OrderByDescending(t => (double.TryParse(t.PressureChange.toString(), out temp) ? temp : 0.0)).ToList();
}
I have not tested it but you can use an extension method like this:
public class StringRealComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
double d1;
double d2;
double.tryParse(s1, out d1);
double.TryParse(s2, out d2);
return double.Compare(d1, d2);
}
}

Better way to check resultset from LINQ projection than List.Count?

Is there a better way to check if a LINQ projection query returns results:
IList<T> TList = db.Ts.Where(x => x.TId == 1).ToList(); // More canonical way for this?
if (TitleList.Count > 0)
{
// Result returned non-zero list!
string s = TList.Name;
}
You can use Any(), or perhaps more appropriately to your example, SingleOrDefault(). Note that if you are expecting more than one result and plan to use all of them, then it doesn't really save anything to use Any() instead of converting to a List and checking the length. If you don't plan to use all the results or you're building a larger query that might change how the query is performed then it can be a reasonable alternative.
var item = db.Ts.SingleOrDefault( x => x.TId == 1 );
if (item != null)
{
string s = item.Name;
...
}
or
var query = db.Ts.Where( x => x.Prop == "foo" );
if (query.Any())
{
var moreComplexQuery = query.Join( db.Xs, t => t.TId, x => x.TId );
...
}

Linq Query - Finding consecutive increasing values

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.

Resources