Query nested linked lists - c#

I'm trying to query a nested linked list where result is the product of a child value multiplied by values in the parent list, and then the result is added.
This code works:
var X = (from B in Main.Globals.BookLL
from G in B.GreeksLL
where B.DealNo == 1 && B.Strategy == "Condor" &&
G.BookOrComp == "Book" && G.GreekType == "Delta"
select new
{
Total = G.Data[3] * B.BookPosn * B.FxRate
}).Sum(s=>s.Total);
...but I'd prefer to use lambda. This code below gives the compile error shown as a comment at the end of the line.
double Z = Globals.BookLL.Where(B => B.DealNo == 1 && B.Strategy == "Condor").
SelectMany(G => G.GreeksLL).
Where(G => G.BookOrComp == "Book" && G.GreekType == "Delta").
Select(G => new { Total = G.Data[3] * B.BookPosn*B.FxRate }). // Compile error "B does not exist in the current context"
Sum();
I don't know how to do this, please take a look and correct the query? Thanks.

Try:
double Z = Globals.BookLL.Where(B => B.DealNo == 1 && B.Strategy == "Condor").
SelectMany(par => par.GreeksLL, (parent, child) => new { G = child, B = parent }).
Where(both => both.G.BookOrComp == "Book" && both.G.GreekType == "Delta").
Select(both => new { Total = both.G.Data[3] * both.B.BookPosn*both.B.FxRate }).
Sum(x => x.Total);
My naming is a bit weird, but I hope you get the idea, basically you 'abandoned' B when you did SelectMany(), and this should be the way.
It is untested, so let me know if it works.
See MSDN for SelectMany() with results selector function.

Another approach is to filter the GreekLL inside the SelectMany using this overload, and then use Sum extension:
double z = Main.Globals.BookLL.Where(book => book.DealNo == 1 && book.Strategy == "Condor")
.SelectMany(book => book.GreeksLL.Where(greek => greek.BookOrComp == "Book" && greek.GreekType == "Delta")
,(book, greek) => new { Greek = greek, Book = book })
.Sum(greekAndBook => greekAndBook.Book.BookPosn * greekAndBook.Book.Fxrate * greekAndBook.Greek.Data[3]);

Related

Interpreting Sql with where and having statement to linq giving different results

Please - I have this SQL statement:
SELECT FK_ClassId, LectureDays
FROM tbl_TimeTables
WHERE Term = 'First Term'
AND FK_session = 4
AND fk_classId = 1
AND (fk_subjectid <> 1)
GROUP BY FK_classId, LectureDays
HAVING (COUNT(*) < 6)
This returns this result:
Image Embedded Here, giving the right result
But when I interpret to linq, I get the a different result:
Tbl_TimeTables.GroupBy(x => new { x.FK_Session, x.Term, x.LectureDays,
x.FK_ClassId, x.FK_SubjectId })
.Where(grp => grp.Count() < 6 && grp.Key.FK_Session == 4 && grp.Key.Term ==
"First Term" && grp.Key.FK_ClassId == 1 && grp.Key.FK_SubjectId != 1)
.Select(grp => new
{
LectureDay = grp.Key.LectureDays,
ClassId = grp.Key.FK_ClassId
})
Wrong Results Picture Link here
Please look at my code, what am I doing wrong?
Thanks
Tim
This is the right way the linq query should go according to Matt Gibson's suggestion:
Tbl_TimeTables
.Where(x => x.FK_Session == 4 && x.Term == "First Term" && x.FK_ClassId == 1
&& x.FK_SubjectId != 1)
.GroupBy(x => new { x.FK_ClassId, x.LectureDays })
.Where(grp => grp.Count() < 6)
.Select(grp => new
{
ClassId = grp.Key.FK_ClassId,
LectureDay = grp.Key.LectureDays
})
This works exactly like the sql
Also to point out that this link: http://www.dotnettricks.com/learn/sqlserver/definition-use-of-group-by-and-having-clause helped me in understanding how the having statement works, which helped in seeing what Matt what saying.

Difference between LINQ Lambda and SQL statement

I have the following lambda statement:
var resources = Db.Resource.Where(w => w.ResValue.Any(a => a.ApplicationFk == applicationPk) && w.CategoryFk == (categoryId ?? w.CategoryFk ) && w.IsEditable);
if (cultureIdsMissing!= null)
{
resources = resources.Where(w => w.ResValue.Any(a => cultureIdsMissing.Any(aa => aa == a.CultureFk) && a.Value == string.Empty));
}
This is not returning the result which I want, which is returned by:
SELECT Resource.ResourcePk, Resource.CategoryFk, Resource.Name, Resource.IsEditable, ResValue.ApplicatieFk, ResValue.CultureFk, ResValue.Value
FROM Resource
INNER JOIN ResValue ON Resource.ResourcePk = ResValue.ResourceFk
WHERE (ResValue.ApplicatieFk = 6)
AND (Resource.IsEditable = 1)
AND (ResValue.Value = '')
AND (ResValue.CultureFk = 1 OR ResValue.CultureFk = 2)
Not that cultureIdsMissing is a List containing both the numbers 1 and 2.
What am I missing or doing wrong with the lambda query?
I think you have to remove && w.CategoryFk == (categoryId ?? w.CategoryFk ) from your linq lemda expression. if categoryId = 1 then it will take only records with value 1. So try after remove that. Your linq code should be this.
var resources = Db.Resource.Where(w => w.ResValue.Any(a => a.ApplicationFk == applicationPk)&& w.IsEditable);
if (cultureIdsMissing!= null)
{
resources = resources.Where(w => w.ResValue.Any(a => cultureIdsMissing.Any(aa => aa == a.CultureFk) && a.Value == string.Empty));
}
You should take it from your sql statement :
Db.Resource
.Join(Db.ResValue
, rs => rs.ResourcePk
, resV => resv.resourceFk
, (rs, resv) => new { res = rs, resV = resV })
.Where(w => w.resv.ApplicatieFk == 6
&& w.res ==1
&& resv.Value == string.empty()
&& (resv.CultureFk == 1 || resv.CultureFk == 2))
It's not tested so maybe it won't work on first try.
I would translate the SQL to query comprehension syntax. In general, convert phrases in query comprehension order, use table aliases as range variables (or create range variables), and put unary/overall aggregate functions (such as TOP, DISTINCT or SUM) as function calls outside the whole query. For your SQL,
var ans = from r in Resource
where r.IsEditable == 1
join rv in ResValue on r.ResourcePk equals rv.ResourceFk
where rv.ApplicatieFk == 6 && rv.Value == "" && (rv.CultureFk == 1 || rv.CultureFk == 2)
select new { r.ResourcePk, r.CategoryFk, r.Name, r.IsEditable, rv.ApplicatieFk, rv.CultureFk, rv.Value };

How to use LINQ to select and transform a subset of a List<T>

I have a list List<UserRoles> roles that has this structure
{r:1,u:1,v:3},
{r:1,u:1,v:5},
{r:2,u:1,v:9},
{r:3,u:2,v:10}
I am trying to write a LINQ statement that will filter out only the "r"s that have values 1 & 2 and return a collection of ints/strings of "v"s
This is what I am trying to do and my problem is in the part where I want to transform the into that holds only the corresponding "v"s.
List<Int32> = roles.Where(r => r.r == 1 || r.r == 2)
.Select(i => new Int32{id = i.v});
This doesn't compile with an error that 'id' is unknown.
the end result that I need is this:
List<Int32>
{v:3},
{v:5},
{v:9}
Sound like you need a list of int:
List<int> result = roles.Where(r => r.r == 1 || r.r == 2)
.Select(i => i.v)
.ToList();
In case you have a list of int to filter, you can use Contains method to avoid lots of ||:
var filters = new[] { 1, 2};
List<int> result = roles.Where(r => filters.Contains(r.r))
.Select(i => i.v)
.ToList();
Or maybe you need {v:9}, you can use anonymous type with var keyword:
var result = roles.Where(r => filters.Contains(r.r))
.Select(i => new { i.v })
.ToList();
I guess v is already an int.
So the solution would be as simple as :
var result = roles.Where(r => r.r == 1 || r.r == 2).Select(i => i.v).ToList();
If what you want is an array of anonymous objects, use this:
var res = roles.Where(r => r.r == 1 || r.r == 2).Select(i => new{i.v}).ToList();
This would produce a list of objects with a single property called v.
If you are looking for a list of integers, and v is an int in the original class, use this:
var res = roles.Where(r => r.r == 1 || r.r == 2).Select(i => i.v ).ToList();
// note that there's no new here ^^^^^
You've used an annoymous type but then added Int32 in front of it which is illegal.
List<Int32> results = roles.Where(r => r.r == 1 || r.r == 2)
.Select(i => new { i.v }).ToList();

LINQ: How can I shorten my code?

I have done some LINQ, it works great but I'm not a fan of this type of coding, I would like to shorten it down, but not quite sure how to.
Does anyone know how I can shorten this section of code? I've heard of predicates before but not quite sure how to implement them?
List<Voucher> list = new List<Voucher>();
if (String.IsNullOrEmpty(Search.SearchText) && Search.Status == 0)
{
list = (from voucherslist in db.Vouchers
//where voucherslist.Status != (int)VoucherStatus.Removed
select voucherslist)
.Take(100)
.ToList();
}
if (!String.IsNullOrEmpty(Search.SearchText) && Search.Status ==0)
{
list = (from voucherslist in db.Vouchers
where voucherslist.Title.Contains(Search.SearchText)
select voucherslist).Take(100).ToList();
}
if (String.IsNullOrEmpty(Search.SearchText) && Search.Status > 0)
{
list = (from voucherslist in db.Vouchers
where voucherslist.Status == Search.Status
select voucherslist).Take(100).ToList();
}
if (!String.IsNullOrEmpty(Search.SearchText) && Search.Status > 0)
{
list = (from voucherslist in db.Vouchers
where voucherslist.Status == Search.Status
&& voucherslist.Title.Contains(Search.SearchText)
select voucherslist).Take(100).ToList();
}
// Convert
ret = VouchersConverter.Convert(list);
// Get Business Details
foreach (ENT_Voucher item in ret)
item.BusinessDetails = this._businessesBLL.GetBusinessDataByID(item.BusinessID);
// Refine and sort
ret = ret.Where(x=>x.BusinessDetails.Accept == true)
.OrderByDescending(x => x.Status.Equals(1))
.ThenByDescending(x => x.StartDate).ToList();
To remove the repetition, first set up your list.
list = (from voucherslist in db.Vouchers
//where voucherslist.Status != (int)VoucherStatus.Removed
select voucherslist);
Then add the title search if you need it:
if (!String.IsNullOrEmpty(Search.SearchText))
{
list = list.Where(x => x.Title.Contains(Search.SearchText));
}
And the status search:
if (Search.Status > 0)
{
list = list.Where(x => x.Status == Search.Status);
}
And finally, take your 100 and flatten it to a list.
list = list.Take(100).ToList();
The thing to bear in mind is that this will not actually construct and execute the SQL query until the .ToList() call, and the SQL that will be executed will contain all of the filtering you have concatenated together.
Your current logic looks a bit broken to me, but I suspect you want:
var query = db.Vouchers;
if (...)
{
query = query.Where(v => v.Title.Contains(Search.SearchText);
}
if (...)
{
query = query.Where(v => v.Status == Search.Status);
}
// etc
List<Voucher> list = query.Take(100).ToList();
Using multiple calls to Where will effectively apply an "AND" on all the filters.

How would I write this as a single LINQ query?

Using the following logic, what would the correct syntax be for a single LINQ query?
If Branch is Service, I want its parent, otherwise I want Branch.
Can you critique my attempt and let me know how I can improve it?
int branchId = 21;
var t = ctx.BranchInfos.Single(p => p.BranchID == branchId );
if (t.Type == BranchType.Service.ToString())
{
t = ctx.BranchInfos.Single(p => p.BranchID == t.Parent);
}
I suggest that if this is only needed in one place then what you have now is reasonably clear and should be kept.
If you are doing this a lot then do something like:
public static BranchInfo BranchOrServiceParent(
this IEnumerable<BranchInfo> input)
{
var t = BranchInfos.Single(p => p.BranchID == branchId);
if (t.Type == BranchType.Service.ToString())
t = input.BranchInfos.Single(p => p.BranchID == t.Parent);
return t;
}
Then using it is as simple as:
int branchId = 21;
var t = ctx.BranchInfos.BranchOrServiceParent();
If you subsequently need to parameterize/change thing things you can in a clear fashion.
If you subsequently find that the two possible trips to the database are a performance issue then you can either try a complex Linq query or accept that this probably needs to actually be done by a stored procedure.
var t = ctx.BranchInfos.Single(
p => (p.BranchID == branchId && p.Type != BranchType.Service.ToString) ||
(p.BranchID == GetBranchParentId(branchId) && p.Type == BranchType.Service.ToString));
where GetBranchParentId is a function wich returns the BranchId of the Branch who's id is passes as a parameter.
But, I like your original code, so I wouldn't use a single query to get my data.
I believe the following is equivalent to your code sample. I have added some mock code to turn this into a self-contained example.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Context ctx = new Context();
ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 20, Parent = 0 });
ctx.BranchInfos.Add(new BranchInfo() { Type = "Service", BranchID = 21, Parent = 20 });
ctx.BranchInfos.Add(new BranchInfo() { Type = "NonService", BranchID = 30, Parent = 20 });
int branchId = 21;
var t = (from a in ctx.BranchInfos
where a.BranchID == branchId
select a.Type != BranchType.Service.ToString() ? a :
(from b in ctx.BranchInfos
where b.BranchID == a.Parent
select b).Single()).Single();
Console.WriteLine(t.BranchID); // Prints 20
}
class Context
{
public List<BranchInfo> BranchInfos = new List<BranchInfo>();
}
class BranchInfo
{
public string Type;
public int BranchID;
public int Parent;
}
enum BranchType
{
Service = 0
}
}
}
This will probably get you what you need unless I messed up the logic.
Edit: Different approach
var t = ctx.BranchInfos.Where(p.BranchID == branchId).First(p => p.Type == BranchType.Service.ToString() ? p.Parent : p);
var t = ctx.BranchInfos.Where(p =>
(
p.BranchID == branchID &&
p.Type != BranchType.Service.ToSting()
)
||
(
p.Type == BranchType.Service.ToSting() &&
ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault() != null &&
p.BranchID == ctx.BranchInfos.Where(p => p.BranchID == branchID).FirstOrDefault().ParentID
)).FirstOrDefault();
The logic here is: (Get me Branch By ID IF the type is of service) OR (get me the parent of a Branch where I know the the child ID if the branch type is Service)
ALSO:
Even though there's a subquery in there, it will evaluate to a single hit to the DB because you're using the same Datacontext in the subquery.
I am quite sure you can do it with a single LINQ statement, but I am equaly sure that you should not do this. It will not improve the readabilty and hardly the performance.
var t = ctx.BranchInfos.Single(x =>
(
x.BranchID == branchID &&
x.Type != BranchType.Service.ToSting()
)
||
(
ctx.BranchInfos.Any(
y.BranchID == branchID) &&
y.Type == BranchType.Service.ToSting()) &&
x.BranchID == ctx.BranchInfos.Single(
y.BranchID == branchID) &&
y.Type == BranchType.Service.ToSting()).ParentID
)
);
Nice, isn't it? :D I still suggest not to use it. The first case is simple - if the item has the correct ID and and is not of type Service we have a match.
The second case is more tricky. We have to check if the item has the ID from the ParentID property of the item with the supplied ID but only if the item with the supplied ID is of type Service. Because we do not know if there is an item with the supplied ID and type Service when we check this, we must first check with Any() if there is such an item and rely on the conditional evaluation of the and.
Provided BranchInfo.Parent is the same type as BranchInfo:
int branchID;
var branchOrParent = db.BranchInfos
.Where(b => b.BranchID == branchID)
.Select(b => b.Type == BranchType.Service.ToString() ? b.Parent : b)
.FirstOrDefault();
This'll probably work but it's a bit awkward.
var t = ctx.BranchInfos.Where(p => p.BranchID == branchId)
.Select(p =>
p.Type != BranchType.Service.ToString()
? p
: ctx.BranchInfos.Single(t => p.Parent == t.BranchId)).FirstOrDefault();
I think you could do something like this:
var t = ctx.BranchInfos.FirstOrDefault(p => p.BranchID == branchId || p.BranchID == t.Parent);
Mike

Categories