Linq to Sql distinct with left join, Lambda - c#

I'm trying to rewrite the query below into a Linq statement.
The query should return a List of "User" objects containing the four properties (Id, FirstName, LastName, Email).
SELECT DISTINCT Id, FirstName, LastName, Email FROM User
LEFT JOIN Booklet ON Booklet.UserId = User.Id
WHERE Booklet.YearReleased = #YearOfRelease
ORDER BY LastName ASC
Now I have something like this, but the Group by is incorrect and it all doesn't look very good coding...i feel like this can be optimized a lot into a single statement but I fail to do so. (demo year '18').
List<User> = new List<User>();
List<int?> lstUserIds = db.Booklet.GroupBy(x => x.UserId && x.YearReleased)
.Where(x => x.YearReleased == 18)
.OrderBy(x => x.LastName)
.Select(x => x.UserId)
.ToList();
foreach (int iUserId in lstUserIds )
lst.Add(db.User.First(x => x.Id == iUserId));

If you use Any() you don't need distinct nor left join to booklet.
var users = db.User
.Where(u => db.Booklet.Any(b => u.Id == b.UserId && b.YearReleased == 18))
.OrderBy(u => u.LastName);

Tr this:
List<User> lst = db.User.Include(B => B.Booklet)
.Where(B => B.YearReleased == 18)
.OrderBy(x => x.LastName)
.Distinct()
.ToList();

I would use a join to get Left Outer Join :
List<int?> lstUserIds = (from b in db.Booklet.Where(x => x.YearReleased == 18)
join on u in User on b.Id equal u.Id into ids
from id in ids.DEfaultIfEmpty
select new { user = u, books = b})
.OrderBy(x => x.user.LastName)
.GroupBy(x -> x.user.UserId)
.Select(x => x.FirstOrDefault())
.ToList();

Related

Linq an aggregate with a grouping

I'm translating a query and here's the original:
select top 5 t.usrID, u.FirstName, u.LastName, t.cnt as sCount
from (
select usrID, COUNT(rID) as cnt
from sessions as s where s.sDate > DATEADD(yy, -1, getdate())
group by usrID
) as t
inner join users as u on t.usrID = u.usrID
order by t.cnt desc
Here's what I have so far:
var topUser = (from p in _context.Sessions
where p.SDate > DateTime.Now.AddYears(-1)
join c in _context.Users on p.UsrId equals c.UsrId into j1
from j2 in j1.DefaultIfEmpty()
// group j2 by p.UsrId into grouped
select new
{
p.UsrId,
j2.FirstName,
j2.LastName,
cnt = p.RId
})
//.OrderBy(d => d.cnt)
//.GroupBy(o => o.UsrId)
.Take(5);
I'm having trouble figuring out how to include count() and group by clauses. When I include groupBy my other columns disappear. Thank you.
This is the answer to your answer - not to your original query. I would put it as comment, but without formatting it's hard to explain
Assuming User object has collection of Session your first statement can be drastically simplified:
var topUsers = _context.Sessions
.Where(s => s.SDate > DateTime.Now.AddYears(-1))
.Select(s => new
{
s.UsrId,
cnt = s.User.Sessions.Count(u => u.UsrId == s.UsrId)
})
.OrderByDescending(s => s.cnt)
.Take(5);
You can shape the results to get a ViewModel that also has FirstName and LastName. It all boils down to defining a model with one-to-many relationship
var topUser = (from p in _context.Sessions
where p.SDate > DateTime.Now.AddYears(-1)
join c in _context.Users on p.UsrId equals c.UsrId into j1
from j2 in j1.DefaultIfEmpty()
group j2 by p.UsrId into g
select new
{
UsrId = g.Key,
FirstName = g.Select(x => x.FirstName).FirstOrDefault(),
LastName = g.Select(x => x.LastName).FirstOrDefault(),
sCount = g.Count()
})
.OrderByDescending(d => d.sCount)
.Take(5);
Alright so it's not the most efficient but it works:
var topUsers = _context.Sessions
.Where(s => s.SDate > DateTime.Now.AddYears(-1))
.GroupBy(s => s.UsrId)
.Select(ws => new { ws.Key, cnt = ws.Count() })
.OrderByDescending(s => s.cnt)
.Take(5);
var topNamedUsers = topUsers.Join(_context.Users, ws => ws.Key, ud => ud.UsrId, (ws, ud) => new { ws.Key, ud.FirstName, ud.LastName, ws.cnt });

LINQ 3 Inner Joins with 1 Left Outer Join

Wondering why LINQ doesn't have a Left Join method. I've been trying to figure this out with myriad examples on SO, but no such luck. The other examples show simple examples with one join. If I group the joins then I only get references to the TradeCountries table in the select statement.
Being new to LINQ, I could've had this done 4 hours ago with a simple SELECT statement, but here I'm am trying to figure out why the LeftJoin method was left out of LINQ.
What does the line with "LeftJoin" need to be changed to make this work?
/*
* GetTop5Distributors
#param int array of series IDs
*/
public List<TopDistributors> Get5TopDistributors(IEnumerable<int> seriesIds)
{
_context = new MySQLDatabaseContext();
var result = _context.TradesTrades
.Join(_context.TradesSeries, tt => tt.SeriesId, ts => ts.Id, (tt, ts) => new { tt, ts })
.Join(_context.TradesTradeDistributors, tsd => tsd.tt.Id, ttd => ttd.TradeId,
(tsd, ttd) => new { tsd, ttd })
.Join(_context.TradesOrganisations, tsdto => tsdto.ttd.DistributorId, to => to.Id,
(tsdto, to) => new { tsdto, to })
.LeftJoin(_context.TradesCountries, tsdc => tsdc.to.CountryId, tc => tc.Id,
(tsdc, tc) => new {tsdc, tc})
.Where(x => seriesIds.Contains(x.tsdc.tsdto.tsd.tt.SeriesId))
.Where(x => x.tsdc.tsdto.tsd.tt.FirstPartyId == null)
.Where(x => x.tsdc.tsdto.tsd.tt.Status != "closed")
.Where(x => x.tsdc.tsdto.tsd.tt.Status != "cancelled")
.GroupBy(n => new { n.tsdc.tsdto.tsd.tt.SeriesId, n.tsdc.tsdto.ttd.DistributorId })
.Select(g =>
new TopDistributors
{
SeriesId = g.Key.SeriesId,
DistributorName = g.Select(i => i.tsdc.to.Name).Distinct().First(),
IsinNickname = g.Select(i => i.tsdc.tsdto.tsd.ts.Nickname).Distinct().First(),
CountryName = g.Select(i => i.tc.Name).Distinct().First(),
CommissionTotal = Math.Ceiling(g.Sum(i => i.tsdc.tsdto.ttd.Commission))
}
)
.OrderByDescending(x => x.CommissionTotal)
.Take(5)
.ToList();
return result;
}
Here's the rather simple select statement that is taking orders or magnitude too long to convert to LINQ.
SELECT
trades_trades.series_id,
trades_organisations.`name`,
trades_series.nickname,
trades_countries.name as Country_Name,
SUM(trades_trade_distributors.commission) as Commission_Total
FROM
trades_trades
JOIN trades_series
ON trades_series.id = trades_trades.series_id
JOIN trades_trade_distributors
ON trades_trades.id = trades_trade_distributors.trade_id
JOIN trades_organisations
ON trades_trade_distributors.distributor_id = trades_organisations.id
LEFT JOIN trades_countries
ON trades_organisations.country_id = trades_countries.id
WHERE trades_trades.series_id IN (
17,
18)
AND trades_trades.first_party_id IS NULL
AND trades_trades.status <> 'closed'
AND trades_trades.status <> 'cancelled'
GROUP BY trades_trades.series_id, trades_trade_distributors.distributor_id
ORDER BY Commission_Total DESC
Following my recipe, here is a more or less straightforward translation of the SQL to LINQ. I moved the where to be near what it constrains, and used let to create a convenient name for the Sum, as LINQ doesn't allow you to forward reference anonymous object members.
var ans = from tt in trades_trades
where new[] { 17, 18 }.Contains(tt.series_id) && tt.first_party_id == null &&
tt.status != "closed" && tt.status != "cancelled"
join ts in trades_series on tt.series_id equals ts.id
join ttd in trades_trade_distributors on tt.id equals ttd.trade_id
join to in trades_orginizations on ttd.distributor_id equals to.id
join tc in trades_countries on to.country_id equals tc.id into tcj
from tc in tcj.DefaultIfEmpty() // GroupJoin -> left join
group new { tt, ts, ttd, to, tc } by new { tt.series_id, ttd.distributor_id } into tradeg
let Commission_Total = tradeg.Sum(trade => trade.ttd.commission)
orderby Commission_Total descending
select new {
tradeg.Key.series_id,
tradeg.First().to.name,
tradeg.First().ts.nickname,
Country_Name = tradeg.First().tc == null ? null : tradeg.First().tc.name,
Commission_Total
};

Translating my SQL Query to c# linq/lambda. Multiple parameter GroupBy

I've been puzzling over this problem all morning and can't figure out how to do it in C#.
My SQL query as follows:
select a.CourseID,
a.UserID
from audit a
inner join results r on a.UserID = r.UserID
inner join Course c on a.CourseID = c.CourseID
where c.CourseType = 9 and a.Guid = 'A123F123D123AS123123'
and a.Result = 'Passed' and r.Class = 'Maths'
group by a.CourseID, a.UserID
order by a.UserID
returns exactly what I want, but I can't seem to translate it into linq format. (the format being used here is what is required in my job at the moment so please advise on this format)
So far I have the following:
var audits = auditRepository.Get(a => a.Course.CourseType == 9 && a.GUID == this.Company.GUID && a.Result == "Passed", null, null,
a => a.Course, a => a.User)
.Join(resultsRepository.Get(r => r.GUID == this.Company.GUID && r.Class == class),
a => a.UserID,
r => r.UserID,
(a, r) => new Audit
{
User = a.User,
Course = a.Course,
Result = a.Result,
Timestamp = a.Timestamp,
AuditID = a.AuditID,
UserID = a.UserID
}
)
.OrderByDescending(o => o.Timestamp)
.GroupBy(u => new { u.User, u.Course })
.Select(grp => grp.ToList())
.ToList();
However this returns duplicates.
Any advice is appreciated, thanks
H
Instead of
.Select(grp => grp.ToList())
Select only the first element from each group to exclude duplicates:
.Select(grp => grp.First())
If you need a count also:
.Select(t => new{grp = t.First(),cnt = t.Count()} )
Fix:
.Select(t => new { grp = t.First(), cnt = t.Select(s => s.AuditID).Distinct().Count() })

Having problems with a Lambda LINQ Query

I'm trying to convert the following SQL expression into a Lambda LINQ query and I seem to be going round in circles at the moment:
select m.MemberExternalPK FROM Member.Member AS m INNER JOIN Member.Account AS a ON m.MemberID = a.MemberID where m.MemberExternalPK in
(
SELECT m.MemberExternalPK
FROM Member.Member AS m INNER JOIN Member.Account AS a ON m.MemberID = a.MemberID
group by MemberExternalPK
having Count(AccountID) = 1
)
and AccountStatusID = 3
So far I have managed to get the following syntax that returns the correct number of rows I am after but all columns (except the MemberExternalPK one I want)!
Members.Join(Accounts, m => m.MemberID, a => a.MemberID, (m, a) => new { m, a })
.GroupBy(t => t.m.MemberExternalPK, t => t.a)
.Where(grp => grp.Count(p => p.AccountID != null) == 1)
.SelectMany(sublist => sublist).Where(x => x.AccountStatusID == 3)
I think this is fairly close:
var query =
from m in Member_Member
join a in Member_Account on m.MemberID equals a.MemberID
group a by m.MemberExternalPK into gas
where gas.Count(ga => ga.AccountID != null) == 1
from ga in gas
where ga.AccountStatusID == 3
select gas.Key;
The only concern is the ga.AccountID != null which means that the gas group may have more than one record so you might end up with more than one gas.Key at the end.
Something like this? Splitting it up could also improve performance.
var externalMembers =
Members.Join(Accounts, m => m.MemberID, a => a.MemberID, (m, a) => new { m, a })
.GroupBy(grp => grp.MemberExternalPK)
.Where(grp => grp.Count() > 1)
.Select(grp => grp.Key);
var result =
Members.Where(w => externalMembers.Contains(w.MemberExternalPK) && w.AccountStatusID == 3)
.Select(s => s.MemberExternalPK)

Entity Framework with Linq, inner Join, Group By, Order By

I have a SQL Query
select Firma.Name as companyName,
Taetigkeit.Taetigkeit as skillName,
SUM(Zeit) as time
from Zeiterfassung
inner join Firma On ZEiterfassung.FirmenID = Firma.ID
inner join Taetigkeit on Zeiterfassung.TaetigkeitID = Taetigkeit.ID
group by Taetigkeit, Firma.Name
order by Firma.Name
And want to "translate" it to linq. Here is what I tried:
var query = db.Zeiterfassung
.Where(x => x.Firma.ID == x.FirmenID && x.TaetigkeitID == x.Taetigkeit.ID)
.GroupBy(x => x.Taetigkeit.Taetigkeit1)
.Select(x => new Evaluation() { skillName = x.Key, time = x.Sum(y => y.Zeit), //skillName = x.Sum(x => x.Zeit), })
.OrderBy(x => x.skillName);
I dont know who to solve this with joins and the group by because all the time when i do a groupBy i cant access the other members.
From data you provided, I think query should look like
from z in db.Zeiterfassung
join f in db.Firma on z.FirmenID equals f.ID
join t in db.Taetigkeit on z.TaetigkeitID equals t.ID
select new { f.Name, t.Taetigkeit, z.Zeit) into x
group x by new { x.Taetigkeit, f.Name } into g
select new {
CompanyName = g.Key.Name,
SkillName = g.Key.Taetigkeit,
Time = g.Sum(i => i.Zeit)
}
Or with navigation properties:
db.Zeiterfassung
.Select(z => new { z.Zeit, z.Taetigkeit.Taetigkeit1, z.Firma.Name })
.GroupBy(x => new { x.Taetigkeit1, x.Name })
.Select(g => new Evaluation {
companyName = g.Key.Name,
skillName = g.Key.Taetigkeit1,
time = g.Sum(y => y.Zeit)
});

Categories

Resources