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
};
Related
I have database table called Absence, which has a column called ABSDate. The type of this column is Datetime.
I'm trying to use a LINQ query to search inside this table. When I use the Contains method to query against other columns, I get results:
Working Query
dataGridView1.DataSource = (from A in context.Absence
join S in context.Stagiaire on A.STG equals S.ID
join G in context.Groupe on S.GRP equals G.CODE
join F in context.FILERE on G.FL equals F.CODE
select new { A.ID, A.ABSDate, A.STG, S.Nom, S.Prenom, S.GRP, G.FL })
.Where(X => X.STG.Contains(SearchBox.Text) ||
X.Nom.Contains(SearchBox.Text) ||
X.Prenom.Contains(SearchBox.Text) ||
X.GRP.Contains(SearchBox.Text))
.Select(x => new { x.ID, Date = x.ABSDate, x.Nom, Prénom = x.Prenom, Filiére = x.FL, Groupe = x.GRP })
.ToList();
However, when I use ABSDate as part of the query, I get the following error:
System.NotSupportedException: 'LINQ to Entities does not recognize the
method 'System.String ToString(System.String)' method, and this method
cannot be translated into a store expression.'
Broken Query
dataGridView1.DataSource = (from A in context.Absence
join S in context.Stagiaire on A.STG equals S.ID
join G in context.Groupe on S.GRP equals G.CODE
join F in context.FILERE on G.FL equals F.CODE
select new { A.ID, A.ABSDate, A.STG, S.Nom, S.Prenom, S.GRP, G.FL })
.Where(X => X.STG.Contains(SearchBox.Text) ||
X.Nom.Contains(SearchBox.Text) ||
X.Prenom.Contains(SearchBox.Text) ||
X.GRP.Contains(SearchBox.Text) ||
X.ABSDate.ToString("dd/MM/yyyy hh:mm").Contains(SearchBox.Text))
.Select(x => new { x.ID, Date = x.ABSDate, x.Nom, Prénom = x.Prenom, Filiére = x.FL, Groupe = x.GRP })
.ToList();
Clearly, I know that this is where the issue lies:
X.ABSDate.ToString("dd/MM/yyyy hh:mm").Contains(SearchBox.Text)
How can I format the SearchBox value to dd/MM/yyyy hh:mm, and search by it using Contains method?
There are two ways to go about this:
1st - client side evaluation
dataGridView1.DataSource = (from A in context.Absence
join S in context.Stagiaire on A.STG equals S.ID
join G in context.Groupe on S.GRP equals G.CODE
join F in context.FILERE on G.FL equals F.CODE
select new { A.ID, A.ABSDate, A.STG, S.Nom, S.Prenom, S.GRP, G.FL })
.Where(X => X.STG.Contains(SearchBox.Text) ||
X.Nom.Contains(SearchBox.Text) ||
X.Prenom.Contains(SearchBox.Text) ||
X.GRP.Contains(SearchBox.Text)
).AsEnumerable().Where(X => X.ABSDate.ToString("dd/MM/yyyy hh:mm").Contains(SearchBox.Text))
.Select(x => new { x.ID, Date = x.ABSDate, x.Nom, Prénom = x.Prenom, Filiére = x.FL, Groupe = x.GRP })
.ToList();
The second would be to format the textbox SearchBox.Text to the format of datetime.
Bear in mind that it should be formatted as a datetime, as there is no like equivalent for dates in linq to sql.
Solution small developed from Athanasios Kataras answer
dataGridView1.DataSource = (from A in context.Absence
join S in context.Stagiaire on A.STG equals S.ID
join G in context.Groupe on S.GRP equals G.CODE
join F in context.FILERE on G.FL equals F.CODE
select new { A.ID, A.ABSDate, A.STG, S.Nom, S.Prenom, S.GRP, G.FL })
.AsEnumerable().Where(X => X.STG.Contains(SearchBox.Text) || X.Nom.Contains(SearchBox.Text) || X.Prenom.Contains(SearchBox.Text) || X.GRP.Contains(SearchBox.Text)|| X.ABSDate.ToString("dd/MM/yyyy hh:mm").Contains(SearchBox.Text))
.Select(x => new { x.ID, Date = x.ABSDate, x.Nom, Prénom = x.Prenom, Filiére = x.FL, Groupe = x.GRP }).ToList();
Thanks again Athanasios Kataras
I need to create a LEFT OUTER JOIN in linq lambda syntax. The SQL I am trying to create a linq equivalent of is:
SELECT DISTINCT
p.PartNum AS PartNum, p.ShortChar01 AS SkuType,
vv.VendorID AS VendorCode,
p.PartDescription AS Description, p.Company AS Company
FROM
Part p WITH (NOLOCK)
INNER JOIN
PartPlant pp ON p.Company = pp.Company AND p.PartNum = pp.PartNum
LEFT OUTER JOIN
Vendor vv On pp.VendorNum = vv.VendorNum
WHERE
p.RefCategory = #refCategory
So as you can see its a fairly simple query joining a few tables. The issue is that it could happen that there is no vendor but we still want the rest of the information hence the left outer join.
My current attempt to recreate this is:
_uow.PartService
.Get()
.Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v });
I am aware that the select isn't returning the same fields at the moment. I have ignored that for now as I am trying to ensure i am getting the correct values first.
The SQL query returns 41 records with 1 record having a null vendor. The linq query returns 40 records obviously not returning the one with the null vendor. I have tried using GroupJoin() and DefaultIfEmpty() but I cannot get it to work.
Any help would be greatly appreciated.
From the comment and links from user2321864, I managed to get it working as follows:
_uow.PartService.Get().Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v })
.SelectMany(y => y.V.DefaultIfEmpty(),
(x, y) => new { PPRC = x.PPRC, Vendor = y })
.Select(r => new Level2Parts()
{
CompanyCode = r.PPRC.Part.Company,
Description = r.PPRC.Part.PartDescription,
PartNum = r.PPRC.Part.PartNum,
SkuType = r.PPRC.Part.ShortChar01,
VendorCode = r.Vendor.VendorID
})
.Distinct();
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() })
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");
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)
});