improve this Linq with 2 Selects to same table - c#

Hi i have this query that populates CompletedWords and CompletedRows with Childs that have Status Completed , the thing is it goes 2 times too the segments table instead of 1:
var query = _context.Submodules.Where(t => t.Id == id)
.Select(e => new Submodules{
Id = e.Id,
Name = e.Name,
Status = e.Status,
Token = e.Token,
ModuleId = e.ModuleId,
Gender = e.Gender,
TotalRows = e.TotalRows,
TotalWords = e.TotalWords,
CompletedWords = e.Segments.Where(a => a.Status == Abr.Recorded).Sum(y=> y.Wordcount),
CompletedRows = e.Segments.Where(a => a.Status == Abr.Recorded).Count()
}).ToList();
that translates to:
SELECT t.ID, t.name, t.status, t.token, t.moduleID,
t.gender, t.total_rows AS TotalRows, t.total_words AS TotalWords,
(
SELECT SUM(a.wordcount)
FROM segments AS a
WHERE (a.status = 1) AND (t.ID = a.submoduleID)
) AS CompletedWords, (
SELECT COUNT(*)
FROM segments AS a0
WHERE (a0.status = 1) AND (t.ID = a0.submoduleID)
) AS CompletedRows
FROM submodules AS t
WHERE t.ID = #__id_0
as you can see to populate the CompletedWords and Rows ,
it runs 2 selects where the Status==1 , just that one its Sum , the other is Count() , how can i merge them into .1 select .
Please advice

I wouldn't worry too much about the SQL that EF generates unless you explicitly see a performance issue. Database engines are pretty adept at self-optimizing.
To answer your question though, this should work:
var query = _context.Submodules.Where(t => t.Id == id)
.Select(e => new {
Id = e.Id,
Name = e.Name,
Status = e.Status,
Token = e.Token,
ModuleId = e.ModuleId,
Gender = e.Gender,
TotalRows = e.TotalRows,
TotalWords = e.TotalWords,
ComletedSegments = e.Segments
.Where(a => a.Status == Abr.Recorded)
.Select(y => new { y.Wordcount })
.ToList()
}).ToList()
.Select(e => new Submodules{
Id = e.Id,
Name = e.Name,
Status = e.Status,
Token = e.Token,
ModuleId = e.ModuleId,
Gender = e.Gender,
TotalRows = e.TotalRows,
TotalWords = e.TotalWords,
CompletedWords = e.Sum(y=> y.Wordcount),
CompletedRows = e.Count()
}).ToList();
The first query selects an anonymous type to select the word count from applicable completed segments. The ToList() materializes this and executes the query. For the segments you could leave off the .Select() though this reduces the data selected to just the column we care about.
The second .Select() populates the view model by summing and counting the segments.

Related

LINQ Query with GroupBy, MAX and Count

What could be the LINQ query for this SQL?
SELECT PartId, BSId,
COUNT(PartId), MAX(EffectiveDateUtc)
FROM PartCostConfig (NOLOCK)
GROUP BY PartId, BSId
HAVING COUNT(PartId) > 1
I am actually grouping by two columns and trying to retrieve max EffectiveDateUtc for each part.
This is what I could write. Stuck up on pulling the top record based on the date.
Also not sure, if this is a optimal one.
//Get all the parts which have more than ONE active record with the pat
//effective date and for the same BSId
var filters = (from p in configs
?.GroupBy(w => new
{
w.PartId,
w.BSId
})
?.Select(g => new
{
PartId = g.Key.PartId,
BSId = g.Key.BSId,
Count = g.Count()
})
?.Where(y => y.Count > 1)
select p)
?.Distinct()?.ToList();
var filteredData = (from p in configs
join f in filters on p.PartId equals f.PartId
select new Config
{
Id = p.Id,
PartId = p.PartId,
BSId = p.BSId,
//EffectiveDateUtc = MAX(??)
}).OrderByDescending(x => x.EffectiveDateUtc).GroupBy(g => new { g.PartId, g.BSId }).ToList();
NOTE: I need the top record (based on date) for each part. Was trying to see if I can avoid for loop.
The equivalent query would be:
var query =
from p in db.PartCostConfig
group p by new { p.PartId, p.BSId } into g
let count = g.Count()
where count > 1
select new
{
g.Key.PartId,
g.Key.BSId,
Count = count,
EffectiveDate = g.Max(x => x.EffectiveDateUtc),
};
If I understand well, you are trying to achieve something like this:
var query=configs.GroupBy(w => new{ w.PartId, w.BSId})
.Where(g=>g.Count()>1)
.Select(g=>new
{
g.Key.PartId,
g.Key.BSId,
Count = g.Count(),
EffectiveDate = g.Max(x => x.EffectiveDateUtc)
});

Perform SELECT statement on another SELECT using LINQ

In my .NET Core app, I have the following in LINQ:
return await db.ApplicationUsers.Where(u=>u.Name.Contains(name) && !u.Deleted && u.AppearInSearch)
.OrderByDescending(u => u.Verified)
.Skip(page * recordsInPage)
.Take(recordsInPage)
.Select(u => new UserSearchResult()
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
}).ToListAsync();
which translates to the following SQL:
SELECT [t].[Name], [t].[Verified],
[t].[PhotoURL], [t].[Id],
[t].[Subdomain]
FROM
(SELECT [u0].*
FROM [AspNetUsers] AS [u0]
WHERE (((CHARINDEX('khaled', [u0].[Name]) > 0) OR ('khaled' = N''))
AND ([u0].[Deleted] = 0))
AND ([u0].[AppearInSearch] = 1)
ORDER BY [u0].[Verified] DESC
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY ) AS [t]
But due to performance issues, Microsoft support suggested that I only query columns of fixed length (not varchar(max)). I was asked to change the SQL query to:
SELECT [t].[Name], [t].[Verified],
[t].[PhotoURL] , [t].[Id], [t].[Subdomain]
FROM
(Select u0.Name, u0.Verified, u0.PhotoURL, u0.id, u0.Subdomain,
u0.Deleted, u0.AppearInSearch FROM [AspNetUsers] ) As [u0]
WHERE (((CHARINDEX('khaled', [u0].[Name]) > 0) OR ('khaled' = N''))
AND ([u0].[Deleted] = 0))
AND ([u0].[AppearInSearch] = 1)
ORDER BY [u0].[Verified] DESC
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY ) AS [t]
Which is a SELECT on another SELECT. Unfortunately I don't know how to do it in LINQ. Can someone please guide me how to make the second SQL query in LINQ?
Thank you
First build the inner select:
var s1 = from u in db.AspNetUsers
select new UserSearchResult
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
};
then use it in the outer select:
return await (from u1 in s1
where u.Name.Contains(name) && !u.Deleted && u.AppearInSearch
orderby u.Verified
select u1)
.Skip(page * recordsInPage)
.Take(recordsInPage);
When you make the output of select query .ToList(), the output is a list again. Over this list you can apply another .Select(). So, as in your previous query, you can do it as:
return await db.ApplicationUsers.Where(u=>u.Name.Contains(name) && !u.Deleted && u.AppearInSearch)
.OrderByDescending(u => u.Verified)
.Skip(page * recordsInPage)
.Take(recordsInPage)
.Select(u => new UserSearchResult()
{
Name = u.Name,
Verified = u.Verified,
PhotoURL = u.PhotoURL,
UserID = u.Id,
Subdomain = u.Subdomain
})
.ToList()
.Select(<your new query>)
.ToListAsync();

SQL ROW_NUMBER() in LINQ Query Syntax

I have this query which I wish to replace with Linq as query syntax:
select
ii.Id, ii.Name as Supplier,
qi.Price1, qi.Price2, qi.Price3,
case when qi.price1 is null then NULL else ROW_NUMBER() OVER(ORDER BY isNull(qi.price1,1000000) ASC) end AS Price1Order,
case when qi.price2 is null then NULL else ROW_NUMBER() OVER(ORDER BY isNull(qi.price2,1000000) ASC) end AS Price2Order,
case when qi.price3 is null then NULL else ROW_NUMBER() OVER(ORDER BY isNull(qi.price3,1000000) ASC) end AS Price3Order
From dbo.InquiryItems ii
left join dbo.quoteItems qi on ii.Id = qi.QuoteItem_InquiryItem
SQL-Query Result:
Id Supplier Price1 Price2 Price3 Price1Order Price2Order Price3Order
1655 Supplier 2 80.00 NULL 40.00 3 NULL 1
1656 Supplier 4 65.00 30.00 42.00 2 1 2
1662 Supplier 1 15.00 35.00 43.00 1 2 3
1670 Supplier 3 250.00 NULL NULL 4 NULL NULL
In C# I need this query as a IQueryable Object. I must filter the query for different parts (one or more) and then group it by Supplier (IdAccount) and SUM the prices. This prices I must rank.
return colQuoteItem = from vQuote in this.vQuoteItemOverviews
where vQuote.IdConstructionStageId == ConstructionStageId
group vQuote by new
{
vQuote.IdAccount
} into g
select new vQuoteItemOverviewSum
{
Id = g.Max(x => x.Id),
Price1Order = null, //Here I need the ROW_NUMBER like in the SQL-Syntax
Price2Order = null, //Here I need the ROW_NUMBER like in the SQL-Syntax
Price3Order = null, //Here I need the ROW_NUMBER like in the SQL-Syntax
price1 = g.Sum(x => x.price1),
price2 = g.Sum(x => x.price2),
price3 = g.Sum(x => x.price3),
}
;
Would this work?
var qi1 = (from qi in quoteItems orderby qi.price1 select new {qi.QuoteItem_InquiryItem}).AsEnumerable().Select((i, index) => new {i.QuoteItem_InquiryItem, Rank = index + 1});
var qi2 = (from qi in quoteItems orderby qi.price2 select new {qi.QuoteItem_InquiryItem}).AsEnumerable().Select((i, index) => new {i.QuoteItem_InquiryItem, Rank = index + 1});
var qi3 = (from qi in quoteItems orderby qi.price3 select new {qi.QuoteItem_InquiryItem}).AsEnumerable().Select((i, index) => new {i.QuoteItem_InquiryItem, Rank = index + 1});
return colQuoteItem = from vQuote in this.vQuoteItemOverviews.AsEnumerable()
where vQuote.IdConstructionStageId == ConstructionStageId
group vQuote by new
{
vQuote.IdAccount
} into g
select new vQuoteItemOverviewSum
{
Id = g.Max(x => x.Id),
Price1Order = qi1.FirstOrDefault(_ => _.IdConstructionStageId == x.Id)?.Rank, //Here i need the ROW_NUMBER like in the SQL-Syntax
Price2Order = qi2.FirstOrDefault(_ => _.IdConstructionStageId == x.Id)?.Rank, //Here i need the ROW_NUMBER like in the SQL-Syntax
Price3Order = qi3.FirstOrDefault(_ => _.IdConstructionStageId == x.Id)?.Rank, //Here i need the ROW_NUMBER like in the SQL-Syntax
price1 = g.Sum(x => x.price1),
price2 = g.Sum(x => x.price2),
price3 = g.Sum(x => x.price3),
}
;

Not counting null values from a linq LEFT OUTER JOIN query

I have this sql query that does exactly what i want but i need it in linq. It returns a few AVC rows and counts how many PersonAVCPermission that has status 1 linked to it
SELECT a.Id, a.Name, a.Address, COUNT(p.AVCID) AS Count
FROM AVC AS a
LEFT OUTER JOIN
(
SELECT PersonAVCPermission.AVCId
FROM PersonAVCPermission
WHERE PersonAVCPermission.Status = 1
) AS p
ON a.Id = p.AVCId
GROUP BY a.Id, a.Name, a.Address
I have this query in linq and it does the same thing except when there are no PersonAVCPermission it still counts 1
var yellows = odc.PersonAVCPermissions.Where(o => o.Status == (int)AVCStatus.Yellow);
var q = from a in odc.AVCs
from p in yellows.Where(o => o.AVCId == a.Id).DefaultIfEmpty()
group a by new { a.Id, a.Name, a.Address } into agroup
select new AVCListItem
{
Id = agroup.Key.Id,
Name = agroup.Key.Name,
Address = agroup.Key.Address,
Count = agroup.Count(o => o.Id != null)
};
Im guessing that with DefaultIfEmpty() it places null rows in the list that then gets counted so i try to exclude them with (o => o.Id != null) but it still counts everything as at least one
If i dont use DefaultIfEmpty() it skips the rows with count 0 completely
How can i exclude them or am i doing it completely wrong?
How about using .Any() and a Let?
var yellows = odc.PersonAVCPermissions.Where(o => o.Status == (int)AVCStatus.Yellow);
var q = from a in odc.AVCs
let Y = (from p in yellows.Where(o => o.AVCId == a.Id) select p).Any()
where Y == true
group a by new { a.Id, a.Name, a.Address } into agroup
select new AVCListItem
{
Id = agroup.Key.Id,
Name = agroup.Key.Name,
Address = agroup.Key.Address,
Count = agroup.Count(o => o.Id != null)
};
You don't need the join, nor the grouping:
var q = from a in odc.AVCs
select new AVCListItem
{
Id = a.Id,
Name = a.Name,
Address = a.Address,
Count = yellows.Where(o => o.AVCId == a.Id).Count()
};

linq after groupby unable to get column values

I am getting data from multiple tables by joining and i want to group data on particular column value but after group by statement i can access my aliases and their properties. What mistake i am making?
public List<PatientHistory> GetPatientHistory(long prid)
{
using(var db = new bc_limsEntities())
{
List<PatientHistory> result =
(from r in db.dc_tresult
join t in db.dc_tp_test on r.testid equals t.TestId into x
from t in x.DefaultIfEmpty()
join a in db.dc_tp_attributes on r.attributeid equals a.AttributeId into y
from a in y.DefaultIfEmpty()
where r.prid == prid
group new {r,t,a} by new {r.testid} into g
select new PatientHistory
{
resultid = r.resultid,
bookingid = r.bookingid,
testid = r.testid,
prid = r.prid,
attributeid = r.attributeid,
result = r.result,
Test_Name = t.Test_Name,
Attribute_Name = a.Attribute_Name,
enteredon = r.enteredon,
Attribute_Type = a.Attribute_Type
}).ToList();
return result;
}
}
You're doing this wrong way. As been said by Jon after grouping the sequences with aliases r,t,a doesn't exist. After grouping you receive the sequence g with sequances of r,t,a in each element of g. If you want get one object from each group (for example most recent) you should try this:
List<PatientHistory> result =
(from r in db.dc_tresult
join t in db.dc_tp_test on r.testid equals t.TestId into x
from t in x.DefaultIfEmpty()
join a in db.dc_tp_attributes on r.attributeid equals a.AttributeId into y
from a in y.DefaultIfEmpty()
where r.prid == prid
group new {r,t,a} by new {r.testid} into g
select new PatientHistory
{
resultid = g.Select(x => x.r.resultid).Last(), // if you expect single value get it with Single()
// .... here add the rest properties
Attribute_Type = g.Select(x => x.a.Attribute_Type).Last()
}).ToList();
I appreciated this question so I thought I would add another potential usage case. I would like feedback on what the cleanest approach is to getting table information through a group operation so that I can project later in the select operation. I ended up combining what the OP did which is to pass objects into his group clause and then used the g.Select approach suggested by YD1m to get table information out later. I have a LEFT JOIN so I'm defending against nulls :
// SQL Query
//DECLARE #idCamp as Integer = 1
//
//select *,
//(select
//count(idActivityMaster)
//FROM tbActivityMasters
//WHERE dftidActivityCategory = A.idActivityCategory) as masterCount
//FROM tbactivitycategories A
//WHERE idcamp = #idCamp
//ORDER BY CategoryName
int idCamp = 1;
var desiredResult =
(from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group new {c, m} by new { m.dftidActivityCategory } into g
select new
{
idActivityCategory = g.Select(x => x.m == null ? 0 : x.m.dftidActivityCategory).First(),
idCamp = g.Select(x => x.c.idCamp).First(),
CategoryName = g.Select(x => x.c.CategoryName).First(),
CategoryDescription = g.Select(x => x.c.CategoryDescription).First(),
masterCount = g.Count(x => x.m != null)
}).OrderBy(o=> o.idActivityCategory);
desiredResult.Dump("desiredResult");
If I just use a basic group approach I get the results but not the extra column information. At least I can't find it once I group.
var simpleGroup = (from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
.OrderBy(o => o.CategoryName)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group m by m == null ? 0 : m.dftidActivityCategory into g
select new
{
// How do I best get the extra desired column information from other tables that I had before grouping
// but still have the benefit of the grouping?
// idActivityCategory = g.Select(x => x.m == null ? 0 : x.m.dftidActivityCategory).First(),
// idCamp = g.Select(x => x.c.idCamp).First(),
// CategoryName = g.Select(x => x.c.CategoryName).First(),
// CategoryDescription = g.Select(x => x.c.CategoryDescription).First(),
// masterCount = g.Count(x => x.m != null)
idActivityCategory = g.Key,
masterCount = g.Count(x => x != null)
});
simpleGroup.Dump("simpleGroup");
Please tear this up. I'm trying to learn and it just seems like I'm missing the big picture here. Thanks.
UPDATE : Cleaned up by moving the work into the group and making the select more straight forward. If I had known this yesterday then this would have been my original answer to the OP question.
int idCamp = 1;
var desiredResult =
(from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group new { c, m } by new
{ idActivityCategory = m == null ? 0 : m.dftidActivityCategory,
idCamp = c.idCamp,
CateGoryName = c.CategoryName,
CategoryDescription = c.CategoryDescription
} into g
select new
{
idActivityCategory = g.Key.idActivityCategory,
idCamp = g.Key.idCamp,
CategoryName = g.Key.CateGoryName,
CategoryDescription = g.Key.CategoryDescription,
masterCount = g.Count(x => x.m != null)
}).OrderBy(o => o.idActivityCategory);
desiredResult.Dump("desiredResult");

Categories

Resources