LINQ- join with subqueries - linq

I am trying to implement the following query in LINQ
SELECT [flh].[InterestRate], [fld].[RegularPaymentAmount]
FROM [FactLoanDetail] AS [fld]
INNER JOIN [FactLoanHistory] AS [flh] ON [fld].[LoanKey] = [flh].[LoanKey]
LEFT OUTER JOIN [FactLoanPayment] AS [flp] ON ([flh].[LoanKey] = [flp].[LoanKey])
AND flp.PostedDateKey = ( SELECT MAX(PostedDateKey) FROM FactLoanPayment
WHERE LoanKey = flh.LoanKey )
AND flp.PaymentSequenceNumber = ( SELECT MAX(PaymentSequenceNumber)
FROM FactLoanPayment WHERE LoanKey = flh.LoanKey )
WHERE [flh].[AsOfDateKey] = 20200415;
This is for DataWarehouse and FactLoanPayment table does not have PK, and can have multiple records for each LoanKey and each PostedDate.
I have tried
var query = from fld in _dbContext.FactLoanDetail
join flh in _dbContext.FactLoanHistory on fld.LoanKey equals flh.LoanKey
join flp in _dbContext.FactLoanPayment on fld.LoanKey equals flp.LoanKey into lp
from flp in lp.OrderByDescending(p => p.PostedDateKey)
.ThenByDescending(p => p.PaymentSequenceNumber)
.Take(1)
where flh.AsOfDateKey == 20200415
select new {flh.InterestRate, fld].[RegularPaymentAmount}
It compiles fine, but at runtime gives me a warning
orderby [p].PostedDateKey desc, [p].PaymentSequenceNumber desc' could not be translated and will be evaluated locally.
and attempts to return all of the records for each loan from the server, not just the latest ones.
I also tried
var query = from fld in _dbContext.FactLoanDetail
join flh in _dbContext.FactLoanHistory on fld.LoanKey equals flh.LoanKey
join flp in _dbContext.FactLoanPayment on fld.LoanKey equals flp.LoanKey into lp
from flp in lp.OrderByDescending(p => p.PostedDateKey)
.ThenByDescending(p => p.PaymentSequenceNumber).Take(1).DefaultIfEmpty()
join dpm in _dbContext.DimPaymentMethod on flp.PaymentMethodKey equals dpm.PaymentMethodKey
where flh.AsOfDateKey == asOfDateKey &&
flp.PostedDateKey == _dbContext.FactLoanPayment.Where(p => p.LoanKey == flp.LoanKey).Max(m => m.PostedDateKey) &&
flp.PaymentSequenceNumber == _dbContext.FactLoanPayment.Where(p => p.LoanKey == flp.LoanKey).Max(m => m.PaymentSequenceNumber)
which also retruns all the records per loan first.
Is there better way to handle this?

the solution was that to use another table (dimLoan) that has a reference to FLP table as virtual property, that allowed EF to resolve relation properly
var query = from fld in _dbContext.FactLoanDetail
join dimLoan in _dbContext.DimLoan on flh.LoanKey equals dimLoan.LoanKey
join flh in _dbContext.FactLoanHistory on fld.LoanKey equals flh.LoanKey
join flp in _dbContext.FactLoanPayment on dimLoan.LoanKey equals flp.LoanKey
where flh.AsOfDateKey == asOfDateKey &&
flp.PostedDateKey == (
dimLoan.FactLoanPayment
.Where(m => m.LoanKey == flp.LoanKey)
.Max(x => x.PostedDateKey)) &&
flp.PaymentSequenceNumber == (
dimLoan.FactLoanPayment
.Where(m => m.LoanKey == flp.LoanKey)
.Max(x => x.PaymentSequenceNumber))

Related

How to read data from a nested select query linq

I have a query like the following which is of type linq.
var querymiangin = (from t1 in _context.Apiapplicant
join t2 in _context.ApiApplicantHistory on t1.Id equals t2.ApiApplicantId
join t3 in _context.EntityType on t2.LastReqStatus equals t3.Id
where t1.IsDeleted == false && t1.LastRequestStatus == t2.Id && t3.Name == "granted"
select new { A = t1, B = t2, Year = t1.ApiRequestDate.Substring(0, 4), Month = t1.ApiRequestDate.Substring(5, 2) } into joined
group joined by new { joined.Year, joined.Month, joined.B.LastReqStatus } into grouped
select grouped.Select(g => new { ApiReqDate = g.A.ApiRequestDate, ApiDate = g.B.Date, ApiLastReqStatus = g.B.LastReqStatus, ApiYear = g.Year, ApiMonth = g.Month })).ToList();
In the select part, ApiReqDate and ApiDate has multiple records. Now my problem is for each group of month and year, I have multiple ApiDate and ApiReqDate records and I want for each group based on a condition (t1.LastRequestStatus == t2.Id && t3.Name == "granted") by using GetPersianDaysDiffDate() method, obtain the difference between ApiReqDate and its related ApiDate records for each month and then find their average in that month.
For doing that, I have written code like this:
var avgDateDiff = querymiangin.DefaultIfEmpty()
.GroupBy(x => new { x.ApiYear, x.ApiMonth }, (key, g) => new
{
key.ApiYear,
key.ApiYear,
Avg = g.Average(y => GetPersianDaysDiffDate(y.ApiReqDate,y.ApiDate))
})
.ToList();
But the problem is each parameter x.ApiYear, x.ApiMonth,y.ApiReqDate,y.ApiDate are unknown and it shows me error. I appreciate if anyone can suggest me a solution for that.
1 - For the first request querymiangin, you don't need to group by statement, change little the code to :
var querymiangin = (from t1 in Apiapplicant
join t2 in ApiApplicantHistory on t1.Id equals t2.ApiApplicantId
join t3 in EntityType on t2.LastReqStatus equals t3.Id
where t1.IsDeleted == false && t1.LastRequestStatus == t2.Id && t3.Name == "granted"
select new
{
ApiReqDate = t1.ApiRequestDate,
ApiDate = t2.Date,
ApiYear = t1.ApiRequestDate.Substring(0, 4),
ApiMonth = t1.ApiRequestDate.Substring(5, 2)
}).ToList();
2 - For the second query avgDateDiff, use GroupBy by ApiYear and ApiMonth and calculate the Average, like :
var avgDateDiff = querymiangin
.GroupBy(x => new { x.ApiYear, x.ApiMonth }, (key, g) => new
{
key.ApiYear,
key.ApiMonth,
Avg = g.Average(y => GetPersianDaysDiffDate(y.ApiReqDate, y.ApiDate))
}).ToList();
I hope you find this helpful.

Invalid Expression term Where

I have following LINQ statement:
from o in Orders
join od in OrderDetails on o.OrderNumber equals od.OrderNumber
join r in RMAs on o.OrderNumber equals r.OrderNumber
join rd in RMADetails on r.RMAnumber equals rd.RMAnumber
from i in Inventory
where( a => ( od.SKU == a.LocalSKU)).DefaultIfEmpty()//error is here
where (r.Status != "Pending" && od.Adjustment == false)
select new
{
r.Status,
o.Name,
o.Company,
o.Address,
o.Address2,
o.City,
o.State,
o.Country,
o.Email,
o.Zip,
o.Phone,
o.ShipName,
o.ShipCompany,
o.ShipAddress,
o.ShipAddress2,
o.ShipCity,
o.ShipCountry,
o.ShipState,
o.ShipPhone,
o.ShipZip,
o.OrderNumber,
o.ShippingTotal,
OrderDate = o.OrderDate,
SerialNumbers = rd.SerialNumbers ?? "",
o.SourceOrderID
}
It's giving Invalid Where term. What I want to use LEFT OUTER JOIN having SQL Equivalent left join Inventory i on od.SKU = i.LocalSKU
Try this:
var qry = from o in Orders
join od in OrderDetails.Where(od=>od.Adjustment == false) on o.OrderNumber equals od.OrderNumber
join i in Inventory on i.LocalSKU equals od.SKU into grp
from g in grp.DefaultIfEmpty()
join r in RMAs.Where(r=>r != 'Pending') on o.OrderNumber equals r.OrderNumber
join rd in RMADetails on r.RMAnumber equals rd.RMAnumber
select new
{
//set of columns here
};

Linq - Subquery count

Problem: Am trying to rewrite this in Linq:
listOfUsersForReviewer is an IEnumerable<User>
int countOfGreenUsers = 0;
foreach (var user in listOfUsersForReviewer)
{
var u = (from reviewitems in context.ReviewItems
join groupaccountlinks in context.GroupAccountLinks on reviewitems.GroupAccountID equals groupaccountlinks.GroupAccountID
join reviews in context.Reviews on reviewitems.ReviewID equals reviews.ReviewID
join applications in context.Applications on reviews.ApplicationID equals applications.ApplicationID
join reviewers in context.Reviewers on applications.ResponsibleReviewerID equals reviewers.ReviewerID
join accounts in context.Accounts on groupaccountlinks.AccountID equals accounts.AccountID
join users in context.RBSUsers on accounts.UserID equals users.UserID
where
users.UserID == user.UserID &&
reviewers.FullyQualifiedLogin == fullyQualifiedLogin &&
reviews.ReviewStatusID == (byte)Enums.ReviewStatus.InProgress &&
reviewitems.ReviewItemStatusID == (byte)Enums.ReviewItemStatus.Unapproved
select reviewitems);
byte colour = (byte)Enums.UserStatusColour.Red;
if (u.Count() == 0)
{
colour = (byte)Enums.UserStatusColour.Green;
countOfGreenUsers++;
}
}
have tried to create an anonymous type, however this doesn't compile.
// select number of green users
var x = from user in listOfUsersForReviewer
from reviewitems in context.ReviewItems
join groupaccountlinks in context.GroupAccountLinks on reviewitems.GroupAccountID equals
groupaccountlinks.GroupAccountID
join reviews in context.Reviews on reviewitems.ReviewID equals reviews.ReviewID
join applications in context.Applications on reviews.ApplicationID equals applications.ApplicationID
join reviewers in context.Reviewers on applications.ResponsibleReviewerID equals
reviewers.ReviewerID
join accounts in context.Accounts on groupaccountlinks.AccountID equals accounts.AccountID
join users in context.RBSUsers on accounts.UserID equals users.UserID
where
users.UserID == user.UserID &&
reviewers.FullyQualifiedLogin == fullyQualifiedLogin &&
reviews.ReviewStatusID == (byte)Enums.ReviewStatus.InProgress &&
reviewitems.ReviewItemStatusID == (byte)Enums.ReviewItemStatus.Unapproved
select new
{
UserID = user.UserID,
CountOfGreen = reviewitems.Count()
};
Add grouping clause
var x = from user in listOfUsersForReviewer
from reviewitems in context.ReviewItems
join groupaccountlinks in context.GroupAccountLinks on reviewitems.GroupAccountID equals
groupaccountlinks.GroupAccountID
join reviews in context.Reviews on reviewitems.ReviewID equals reviews.ReviewID
join applications in context.Applications on reviews.ApplicationID equals applications.ApplicationID
join reviewers in context.Reviewers on applications.ResponsibleReviewerID equals
reviewers.ReviewerID
join accounts in context.Accounts on groupaccountlinks.AccountID equals accounts.AccountID
join users in context.RBSUsers on accounts.UserID equals users.UserID
where
users.UserID == user.UserID &&
reviewers.FullyQualifiedLogin == fullyQualifiedLogin &&
reviews.ReviewStatusID == (byte)Enums.ReviewStatus.InProgress &&
reviewitems.ReviewItemStatusID == (byte)Enums.ReviewItemStatus.Unapproved
group user by user.UserID into grouping
select new
{
UserID = grouping.Key,
CountOfGreen = grouping.Count()
};
I ended up rethinking me logic and split this query into 2 simpler ones.
Tools I used were: SQL Server Management Studio (graphical representation), Linqer and potentially Linqpad
And writing the query in English helped a lot.

How to write this LINQ Query in a better way

I have one Linq Query. When I run the query, Only for 10 records its taking 13 seconds to extract the data to the model. I need to know the query which I wrote is good for performance or not. Please guide me what i am doing wrong.
Code
var stocktakelist = (from a in Db.Stocktakes
select new ExportStock
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = (from admin in Db.AdminAccounts where admin.Id == a.Id select admin.Name).FirstOrDefault(),
CreatedOn = a.CreatedOn,
Status = (from items in Db.Items where items.ItemNo == a.ItemNo select items.ItemStatu.Description).FirstOrDefault(),
Title = (from tit in Db.BibContents where tit.BibId == (from bibs in Db.Items where bibs.ItemNo == a.ItemNo select bibs.BibId).FirstOrDefault() && tit.TagNo == "245" && tit.Sfld == "a" select tit.Value).FirstOrDefault() // This line of Query only makes the performance Issue
}
).ToList();
Thanks
The reason this is so slow is because it is running the 3 inner LINQ statements for every item in the outer LINQ statement.
Using LINQ joins will run only 4 queries and then link them together, which is faster.
To find out how to join, there are plenty of resources on the Internet depending on the type of LINQ you are using.
If you're retrieving this data from a SQL server, perhaps consider doing this intensive work in SQL - this is what SQL was designed for and it's much quicker than .NET. EDIT: As highlighted below, the work is done in SQL if using LINQ to SQL/Entities and using the correct join syntax.
I was trying to create the corresponding query with some joins for practice.
I cannot test it and i'm not 100% sure that this query will you get the result
you are hoping for but maybe at least it will give you a hint on how to write
joins with linq.
from a in Db.Stocktakes
join admin in Db.AdminAccounts
on a.Id equals admin.Id
into adminJoinData
from adminJoinRecord in adminJoinData.DefaultIfEmpty( )
join items in Db.Items
on a.ItemNo equals items.ItemNo
into itemsJoinData
from itemsJoinRecord in itemsJoinData.DefaultIfEmpty( )
join title in Db.BibContents
(
from subQuery in Db.BibContents
where subQuery.TagNo == "245"
where subQuery.Sfld == "a"
select subquery
)
on title.BibId equals itemsJoinRecord.BidId
into titleJoinData
from titleJoinRecord in titleJoinData.DefaultIfEmpty( )
select new ExportStock( )
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = adminJoinRecord.Name,
CreatedOn = a.CreatedOn,
Status = itemsJoinRecord.ImemStatu.Description,
Title = titleJoinRecord.Value
}
As others have said, you should use Left Outer Joins in your LINQ just as you would if writing it in SQL.
Your query above will end up looking roughly like this once converted (this is untested, but gives the basic idea):
var a = from a in Db.Stocktakes
join admin in Db.AdminAccounts on admin.Id equals a.Id into tmpAdmin
from ad in tmpAdmin.DefaultIfEmpty()
join item in Db.Items on item.ItemNo equals a.ItemNo into tmpItem
from it in tmpItem.DefaultIfEmpty()
join title in Db.BibContents on bib.BibId equals items.BibId into tmpTitle
from ti in tmpTitle.DefaultIfEmpty()
where ti.TagNo == "245"
&& ti.Sfld == "a"
select new ExportStock
{
Id = a.Id,
ItemNo = a.ItemNo,
AdminId = ad == null ? default(int?) : ad.Id,
CreatedOn = a.CreatedOn,
Status = it == null ? default(string) : it.ItemStatus.Description,
Title = ti == null ? default(string) : ti.Value
};
Using lambda expressions your query will look like this:
Db.Stocktakes
.Join(Db.AdminAccounts, a => a.Id, b => b.Id, (a,b) => new { a, AdminId = b.Name })
.Join(Db.Items, a => a.ItemNo, b => b.ItemNo, (a,b) => new { a, Status = b.ItemStatus.Description, BidId = b.BibId })
.Join(Db.BibContents, a => a.BibId, b => b.BibId, (a,b) => new { a, Value = b.Value, TagNo = b.TagNo, Sfld = b.Sfld })
.Where(a => a.TagNo == "245" && a.Sfld == "a")
.Select(a =>
new ExportStock { Id = a.Id,
ItemNo = a.ItemNo,
AdminId = a.AdminId,
CreatedOn = a.CreatedOn,
Status = a.Status,
Title = a.Value
}
).ToList();

How do I use subquery, groupby, max, and top in single linqToSql statement?

Using LinqToSql, I need to return a single (L) for the most recent modDate in a join table (CL).
Tables:
L (Lid, meta1, meta2, ...)
CL (Cid, Lid, ModDate)
Here is sql that produces the expected result
SELECT l.*
FROM L l
INNER JOIN (
SELECT TOP 1 cl.Lid, MAX(cl.ModDate) as ModDate
FROM CL cl
INNER JOIN L l ON cl.Lid = l.Lid AND l.meta1 = 5
GROUP BY cl.Lid
ORDER BY MAX(cl.ModDate) DESC
) As m ON l.Lid = m.Lid
Simple enough. The subquery projects us to the ids. The query fetches those records with matching ids.
var subquery = db.L
.Where(L => L.meta1 = 5)
.SelectMany(L => L.CLs)
.GroupBy(CL => CL.Lid)
.OrderByDescending(g => g.Max(CL => CL.ModDate))
.Select(g => g.Key)
.Take(1)
var query = db.L
.Where(L => subquery.Any(id => L.Lid == id))
Reflecting on this further, you can get away from the subquery:
var query = db.L
.Where(L => L.meta1 = 5)
.SelectMany(L => L.CLs)
.GroupBy(CL => CL.Lid)
.OrderByDescending(g => g.Max(CL => CL.ModDate))
.Select(g => g.First().L);
As your provided query, I can interpret into this Linq.
var query = from l in Context.L
join m in (from cl in Context.CL
join l in Context.L on cl.Lid equals l.Lid
where l.meta1 == 5
group new { l.Lid, cl.ModDate } by cl.Lid into grp
select new { Lid = grp.Key, ModDate = grp.Max(g => g.ModDate) } into grp
order by grp.ModDate descending
select grp).Take(1) on l.Lid equals m.Lid
select l;
My SQL-fu isn't fabulous and it's before my first coffee, so I assume "l" in the outer query ends up being a completely different "l" to the one in the subquery?
I think this will do it, but you'll have to try to be sure :) It'll be well worth checking what the generated SQL looks like. If you didn't mind it executing as two queries, of course, it would be somewhat simpler.
// Can't do the "Take(1)" here or it will be executed separately
var subquery = from cl in context.CL
join l in context.L on cl.Lid = l.Lid
where l.meta1 = 5 // could put this in join clause
group cl.ModDate by cl.lid into grouped
order by grouped.Max() descending
select grouped.Key;
// But can take the first result of the join
// This may be simpler using dot notation instead of a query expression
var query = (from l in context.L
join lid in subquery
select l).Take(1);
(EDIT: I wasn't taking the max ModDate before. Doh. Also simplified grouping by using the ID as the key (which it was already) so we only need the ModDate as the group values.)

Resources