I've been following a few examples from this website on how to create Linq Left Outer Join queries but I haven't found any examples of where the "outer key in the left join doesn't point to the inner key but instead points to a previous key". Bear with me for that phrasing I know it's not correct but have a look at the following snippets of code and maybe it will be clearer.
Specifically, see the first left join where sp.SalesPersonID = j.SalesPersonID.
select rt.Name as ResourceType, s.FirstName + ' ' + s.Surname as Supervisor, sp.FirstName + ' ' + sp.LastName as SalesPerson, tr.OrderCodeID, tr.SkillID
, j.CustomerName, j.JobNumber
from dbo.TaskResource tr join projects.Task t on t.ID = tr.taskiD
join dbo.ResourceType rt on rt.ID = tr.ResourceTypeID
join projects.projecttask pt on pt.taskid = tr.taskid
join projects.jobproject jp on jp.projectid = pt.projectid
join crm.tbljobs j on j.jobid = jp.jobid
left join common.tblSalesPersons sp on sp.SalesPersonID = j.SalesPersonID
left join common.tblSupervisors s on s.SupervisorID = j.SupervisorID
where JobDeleted is null or JobDeleted = 0
order by ResourceType
When converted to Linq it would make
...from j in temp1.DefaultIfEmpty()
join sp in dbc.tblSalesPersons on j.SalesPersonID equals sp.SalesPersonID into temp2
So far so good. But when I do the next left join I though it would just be the same thing but pointing to one of the previous keys as I mentioned earlier so instead of using the sp variable which I've seen several examples of, I use the j variable which is from a previous join:
from sp in temp2.DefaultIfEmpty()
join s in dbc.tblSupervisors on j.SupervisorID equals s.SupervisorID
Here is the full code snippet:
List<ResourceTreeObject> resourceTreeObjects = (
from tr in dbc.TaskResources
join t in dbc.Tasks on tr.TaskID equals t.ID
join rt in dbc.ResourceTypes on tr.ResourceTypeID equals rt.ID
join pt in dbc.ProjectTasks on tr.TaskID equals pt.TaskID
join jp in dbc.JobProjects on pt.ProjectID equals jp.ProjectID
join j in dbc.tblJobs on jp.JobID equals j.JobID into temp1
from j in temp1.DefaultIfEmpty()
join sp in dbc.tblSalesPersons on j.SalesPersonID equals sp.SalesPersonID into temp2
from sp in temp2.DefaultIfEmpty()
join s in dbc.tblSupervisors on j.SupervisorID equals s.SupervisorID
where j.JobDeleted == null || j.JobDeleted == 0
select new ResourceTreeObject
{
TaskResourceID = tr.ID
,
TaskID = tr.TaskID
,
ResourceTypeID = tr.ResourceTypeID
,
ResourceType = rt.Name
,
SkillID = tr.SkillID
,
OrderCodeID = tr.OrderCodeID
,
PermissionID = tr.PermissionID
,
JobID = j.JobID
,
JobNumber = j.JobNumber
,
CustomerName = j.CustomerName
,
Salesperson = sp.FirstName + " " + sp.LastName
,
Supervisor = s.FirstName + " " + s.Surname
}).ToList();
And this results in the wrong query. The last "left join" is treated like an inner join and returns the wrong number of rows. So in essence what I'm asking is, how do I (in LinQ) do two consecutive left outer joins after doing several consecutive inner joins but use the key from one of the previous tables in my left out join?
Also I'm not sure what the correct terminology for inner/outer keys etc. hence the awkward phrasing and title. Perhaps someone could correct that so it would be more beneficial to others. Thank you.
Your LINQ translation is just a little off.
The SQL has an inner join on crm.tbljobs followed by outer joins on common.tblSalesPerson and common.tblSupervisors.
The LINQ has outer joins on dbc.tblJobs and dbc.tblSalesPersons followed by an inner join on dbc.tblSupervisors.
into temp1 ... from j in in temp1.DefaultIfEmpty() makes the outer join happen on the table introduced prior to the into, which is dbc.tblJobs.
So it should be:
...
// inner join
join j in dbc.tblJobs on jp.JobID equals j.JobID
// left outer join
join sp in dbc.tblSalesPersons on j.SalesPersonID equals sp.SalesPersonID into salesPersons
from sp in salesPersons.DefaultIfEmpty()
// left outer join
join s in dbc.tblSupervisors on j.SupervisorID equals s.SupervisorID into supervisors
from s in supervisors.DefaultIfEmpty()
...
I changed temp1 and temp2 to more meaningful names to demonstrate what they represent in the outer join syntax. Note the relationship and relative position of dbc.tblSalesPersons to salesPersons, for example.
One more thing to remember is that sp and s can be null, so make sure you check for that before accessing their FirstName, LastName, and Surname properties.
I am converting an SQL query to LINQ. It has multiple inner, left and right joins. I'm checking the generated SQL from LINQ in every step. But the problem is based on the selection the generated sql query changes.
Below my Linq
var a = from freight in billingEntity.FreightCharges.AsNoTracking()
join service in billingEntity.ServiceTypes.AsNoTracking()
on freight.ServiceTypeId equals service.Id
join transport in billingEntity.TransportationTypes.AsNoTracking()
on freight.TransportationTypeId equals transport.Id
join division in billingEntity.DivisionDetails.AsNoTracking()
on freight.OriginId equals division.OriginID
join mail in billingEntity.Mailclasses.AsNoTracking()
on freight.MailClassId equals mail.MailClassId
into mailClassFreight
from mail in mailClassFreight.DefaultIfEmpty()
join process in billingEntity.ProcessingCategories.AsNoTracking()
on freight.ProcessingcategoryId equals process.ProcessingCategoryId
into processFreight
from processCategory in processFreight.DefaultIfEmpty()
select mail;
string v = a.ToString();
This generated SQL as
SELECT
[Extent3].[MailClassId] AS [MailClassId],
[Extent3].[MailClassName] AS [MailClassName],
[Extent3].[CreatedDate] AS [CreatedDate]
FROM [dbo].[FreightCharges] AS [Extent1]
INNER JOIN [dbo].[DivisionDetails] AS [Extent2] ON [Extent1].[OriginId] = [Extent2].[OriginID]
LEFT OUTER JOIN [dbo].[Mailclass] AS [Extent3] ON [Extent1].[MailClassId] = [Extent3].[MailClassId]
WHERE ([Extent1].[ServiceTypeId] IS NOT NULL) AND ([Extent1].[TransportationTypeId] IS NOT NULL)
Where I can see the dbo.ProcessingCategory is missing in the join.
If I do select processCategory the SQL generates with missing dbo.MailClass.
The SQL query I am trying to convert to LINQ is below
select * from
dbo.FreightCharges as fc
inner join dbo.ServiceType as st
on fc.ServiceTypeId = st.Id
inner join dbo.TransportationTypes as tt
on fc.TransportationTypeId = tt.Id
inner join dbo.DivisionDetails as dd
on fc.OriginId = dd.OriginId
left join dbo.Mailclass as mc
on fc.MailClassId = mc.MailClassId
left join dbo.ProcessingCategory as pc
on fc.ProcessingcategoryId = pc.ProcessingCategoryId
right join dbo.ContentTitle as ct
on fc.ContentTitleId = ct.ContentTitleId
inner join dbo.AccountDetails as ac
on ct.AccountId = ac.AccountId
where
fc.EffectiveThruDate >= '9999-12-31'
or
fc.EffectiveThruDate is null
You can change your query joins to left joins like this.
select * from
dbo.ContentTitle ct
inner join dbo.AccountDetails as ac
on ct.AccountId = ac.AccountId
left join dbo.FreightCharges as fc
on fc.ContentTitleId = ct.ContentTitleId and fc.EffectiveThruDate >= '9999-12-31'
left join dbo.ServiceType as st
on fc.ServiceTypeId = st.Id
left join dbo.TransportationTypes as tt
on fc.TransportationTypeId = tt.Id
left join dbo.DivisionDetails as dd
on fc.OriginId = dd.OriginId
left join dbo.Mailclass as mc
on fc.MailClassId = mc.MailClassId
left join dbo.ProcessingCategory as pc
on fc.ProcessingcategoryId = pc.ProcessingCategoryId
Linq equivalent
from ct in ContentTitles
join ac in AccountDetails on ct.AccountId equals ac.AccountId
join fc in FreightCharges on ct.ContentTitleId equals fc.ContentTitleId into lfc
from flfc in lfc.Where(f => f.EffectiveThruDate >= new DateTime(9999,12,31)).DefaultIfEmpty()
join st in ServiceTypes on flfc.ServiceTypeId equals st.Id into lst
from flst in lst.DefaultIfEmpty()
join tt in TransportationTypes on flfc.TransportationTypeId equals tt.Id into ltt
from fltt in ltt.DefaultIfEmpty()
join dd in DivisionDetails on flfc.OriginId equals dd.OriginId into ldd
from fldd in ldd.DefaultIfEmpty()
join mc in Mailclasses on flfc.MailClassId equals mc.MailClassId into lmc
from flmc in lst.DefaultIfEmpty()
join pc in ProcessingCategories on flfc.ProcessingcategoryId equals pc.ProcessingCategoryId into lpc
from flpc in lst.DefaultIfEmpty()
select new {ct, ac, lfc, lst ,ltt, ldd, lmc, lpc}
I have a SQL statement
SELECT
cci.[ID], cci.[OwnerFirstName], cci.[OwnerLastName], cci.[CompanyName],
cci.[AddressLine1], cci.[AddressLine2], cci.[AddressLine3], cci.[AddressLine4],
cci.[AddressLine5], cci.[AddressCity], sp1.[Abbreviation] AS [StateAbbreviation],
sp2.[Abbreviation] AS [ProvinceAbbreviation], cci.[AddressPostalCode],
cr1.[Name] AS [CountryName], cr2.[Name] AS [RegionName], cci.[AddressNote],
cci.[Phone1], cci.[Phone2], cci.[Phone3], cci.[Phone4], cci.[Phone5],
cci.[Fax1], cci.[Fax2], cci.[Fax3], cci.[Fax4], cci.[Fax5], cci.[Email1],
cci.[Email2], cci.[Email3], cci.[Email4], cci.[Email5], cci.[CompanyWebSite],
ci.[EIN], ci.[SSN], cli.[LegalName], cli.[LegalAddressLine1], cli.[LegalAddressLine2],
cli.[LegalAddressLine3], cli.[LegalAddressLine4], cli.[LegalAddressLine5],
cli.[LegalAddressCity], sp3.[Abbreviation] AS [LegalAddressStateAbbreviation],
sp4.[Abbreviation] AS [LegalAddressProvinceAbbreviation], cli.[LegalAddressPostalCode],
cr3.[Name] AS [LegalAddressCountryName], cr4.[Name] AS [LegalAddressRegionName],
cli.[LegalAddressNote], cri.[FirstMonthFiscalYear], cri.[FirstMonthIncomeTaxYear],
CONCAT(CONCAT(CONCAT(itfl.[TaxForm], ' - ('),itfl.[Description]), ')') AS [TaxForm],
c.[CryptoPassPhrase], c.[CryptoVector], c.[CryptoMinSALTLen],
c.[CryptoMaxSALTLen], c.[CryptoKeySize], c.[CryptoHash], c.[CryptoSALT],
c.[CryptoIterations], ga.[AccountCode], ga.[UseAnalytics],
CONCAT(CONCAT(CONCAT(CONCAT(e1.[LastName], ', '), e1.[FirstName]), ' '), e1.[MiddleName]) AS [CreatedBy],
sd.[CreatedByID], sd.[CreatedDateTime],
CONCAT(CONCAT(CONCAT(CONCAT(e2.[LastName], ', '), e2.[FirstName]), ' '), e2.[MiddleName]) AS [ModifiedBy],
sd.[ModifiedByID], sd.[ModifiedDateTime], sd.[SiteDescription],
s.[SMTPServer], s.[SMTPUserName], s.[SMTPUserName], s.[SMTPPassword],
s.[SMTP_TO], s.[SMTP_CC], s.[SMTP_BCC], s.[SMTPEncoding]
FROM [settings].[CompanyContactInformation] cci
LEFT OUTER JOIN [settings].[CompanyIdentification] ci ON cci.[ID] = ci.[SiteID]
LEFT OUTER JOIN [settings].[CompanyLegalInformation] cli ON cci.[ID] = cli.[ID]
LEFT OUTER JOIN [settings].[CompanyReportInformation] cri ON cci.[ID] = cri.[SiteID]
LEFT OUTER JOIN [settings].[Cryptography] c ON cci.[ID] = c.[SiteID]
LEFT OUTER JOIN [settings].[GoogleAnalytics] ga ON cci.[ID] = ga.[SiteID]
LEFT OUTER JOIN [settings].[IncomeTaxFormList] itfl ON cri.[TaxFormID] = itfl.[ID]
LEFT OUTER JOIN [settings].[SiteDetails] sd ON cci.[ID] = sd.[SiteID]
LEFT OUTER JOIN [settings].[SMTP] s ON cci.[ID] = s.[SiteID]
LEFT OUTER JOIN [dbo].[Employee] e1 ON sd.[CreatedByID] = e1.[ID]
LEFT OUTER JOIN [dbo].[Employee] e2 ON sd.[ModifiedByID] = e2.[ID]
LEFT OUTER JOIN [dbo].[CountryOrRegion] cr1 ON cci.[AddressCountryID] = cr1.[ID]
LEFT OUTER JOIN [dbo].[CountryOrRegion] cr2 ON cci.[AddressRegionID] = cr2.[ID]
LEFT OUTER JOIN [dbo].[CountryOrRegion] cr3 ON cli.[LegalAddressCountryID] = cr3.[ID]
LEFT OUTER JOIN [dbo].[CountryOrRegion] cr4 ON cli.[LegalAddressRegionID] = cr4.[ID]
LEFT OUTER JOIN [dbo].[StateOrProvince] sp1 ON cci.[AddressStateID] = sp1.[ID]
LEFT OUTER JOIN [dbo].[StateOrProvince] sp2 ON cci.[AddressProvinceID] = sp2.[ID]
LEFT OUTER JOIN [dbo].[StateOrProvince] sp3 ON cli.[LegalAddressStateID] = sp3.[ID]
LEFT OUTER JOIN [dbo].[StateOrProvince] sp4 ON cli.[LegalAddressProvinceID] = sp4.[ID]
So far I have the following LINQ statement in LinqPad:
var q = (
from cci in CompanyContactInformation
join sp1 in StateOrProvince on cci.AddressStateID equals sp1.ID into t1
from rt1 in t.DefaultIfEmpty()
select new {
cci.ID, cci.OwnerFirstName, cci.OwnerLastName, cci.CompanyName,
cci.AddressLine1, cci.AddressLine2,cci.AddressLine3,
cci.AddressLine4, cci.AddressLine5, cci.AddressCity,
StateAbbreviation = sp1.Abbreviation
}).ToList();
q.Dump();
And I am getting the following error:
'LINQPad.User.StateOrProvince' is a 'type' but is used like a 'variable'
The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'.
What am I doing wrong here?
Solved it after pulling my hair out. Had to pluralize some of the tables. Here is the proper LINQ statement:
var q = (
from cci in CompanyContactInformation
join ci in CompanyIdentifications on cci.ID equals ci.SiteID into t1 from rt1 in t1.DefaultIfEmpty()
join cli in CompanyLegalInformation on cci.ID equals cli.ID into t2 from rt2 in t2.DefaultIfEmpty()
join cri in CompanyReportInformation on cci.ID equals cri.SiteID into t3 from rt3 in t3.DefaultIfEmpty()
join c in Cryptographies on cci.ID equals c.SiteID into t4 from rt4 in t4.DefaultIfEmpty()
join ga in GoogleAnalytics on cci.ID equals ga.SiteID into t5 from rt5 in t5.DefaultIfEmpty()
join itfl in IncomeTaxFormLists on rt3.TaxFormID equals itfl.ID into t6 from rt6 in t6.DefaultIfEmpty()
join sd in SiteDetails on cci.ID equals sd.SiteID into t7 from rt7 in t7.DefaultIfEmpty()
join s in SMTPs on cci.ID equals s.SiteID into t8 from rt8 in t8.DefaultIfEmpty()
join e1 in Employees on rt7.CreatedByID equals e1.ID into t9 from rt9 in t9.DefaultIfEmpty()
join e2 in Employees on rt7.ModifiedByID equals e2.ID into t10 from rt10 in t10.DefaultIfEmpty()
join cr1 in CountryOrRegions on cci.AddressCountryID equals cr1.ID into t11 from rt11 in t11.DefaultIfEmpty()
join cr2 in CountryOrRegions on cci.AddressRegionID equals cr2.ID into t12 from rt12 in t12.DefaultIfEmpty()
join cr3 in CountryOrRegions on rt2.LegalAddressCountryID equals cr3.ID into t13 from rt13 in t13.DefaultIfEmpty()
join cr4 in CountryOrRegions on rt2.LegalAddressRegionID equals cr4.ID into t14 from rt14 in t14.DefaultIfEmpty()
join sp1 in StateOrProvinces on cci.AddressStateID equals sp1.ID into t15 from rt15 in t15.DefaultIfEmpty()
join sp2 in StateOrProvinces on cci.AddressProvinceID equals sp2.ID into t16 from rt16 in t16.DefaultIfEmpty()
join sp3 in StateOrProvinces on rt2.LegalAddressStateID equals sp3.ID into t17 from rt17 in t17.DefaultIfEmpty()
join sp4 in StateOrProvinces on rt2.LegalAddressProvinceID equals sp4.ID into t18 from rt18 in t18.DefaultIfEmpty()
select new {
cci.ID, cci.OwnerFirstName, cci.OwnerLastName, cci.CompanyName, cci.AddressLine1, cci.AddressLine2,
cci.AddressLine3, cci.AddressLine4, cci.AddressLine5, cci.AddressCity, StateAbbreviation = rt15.Abbreviation,
ProvinceAbbreviation = rt16.Abbreviation, cci.AddressPostalCode, CountryName = rt11.Name,
RegionName = rt12.Name, cci.AddressNote, cci.Phone1, cci.Phone2, cci.Phone3, cci.Phone4, cci.Phone5,
cci.Fax1, cci.Fax2, cci.Fax3, cci.Fax4, cci.Fax5, cci.Email1, cci.Email2, cci.Email3, cci.Email4,
cci.Email5, cci.CompanyWebSite, rt1.EIN, rt1.SSN, rt2.LegalName, rt2.LegalAddressLine1, rt2.LegalAddressLine2,
rt2.LegalAddressLine3, rt2.LegalAddressLine4, rt2.LegalAddressLine5, rt2.LegalAddressCity,
LegalAddressStateAbbreviation = rt17.Abbreviation, LegalAddressProvinceAbbreviation = rt18.Abbreviation,
rt2.LegalAddressPostalCode, LegalAddressCountryName = rt13.Name, LegalAddressRegionName = rt14.Name,
rt2.LegalAddressNote, rt3.FirstMonthFiscalYear, rt3.FirstMonthIncomeTaxYear,
TaxForm = rt6.TaxForm + " - (" + rt6.Description + ")",
rt4.CryptoPassPhrase, rt4.CryptoVector, rt4.CryptoMinSALTLen, rt4.CryptoMaxSALTLen, rt4.CryptoKeySize, rt4.CryptoHash,
rt4.CryptoSALT, rt4.CryptoIterations, rt5.AccountCode, rt5.UseAnalytics,
CreatedBy = rt9.LastName + ", " + rt9.FirstName + " " + rt9.MiddleName,
rt7.CreatedByID, rt7.CreatedDateTime,
ModifiedBy = rt10.LastName + ", " + rt10.FirstName + " " + rt10.MiddleName,
rt7.ModifiedByID, rt7.ModifiedDateTime, rt7.SiteDescription, rt8.SMTPServer, rt8.SMTPUserName,
rt8.SMTPPassword, rt8.SMTP_TO, rt8.SMTP_CC, rt8.SMTP_BCC, rt8.SMTPEncoding
}).ToList();
q.Dump();
I have 2 inner joins (3 tables) but I don't know and I find it hard to implement my research about outer join in LINQ. How do I change the last inner join to outer join, such that column will still join even if the column (Role) is null?
Here's an existing SQL version of this which I want to convert to LINQ:
SELECT dbo.EmployeeAccess.id, dbo.EmployeeAccess.EmpNo, dbo.EmployeeAccess.RoleID, dbo.EmployeeAccess.Active, dbo.EmployeeAccessLevel.Role,
dbo.View_HCM.LNameByFName
FROM dbo.EmployeeAccess LEFT OUTER JOIN
dbo.EmployeeAccessLevel ON dbo.EmployeeAccess.RoleID = dbo.EmployeeAccessLevel.id INNER JOIN
dbo.View_HCM ON dbo.EmployeeAccess.EmpNo = dbo.View_HCM.EmpNo
LINQ I now have with 2 inner joins:
(from ea in context.EmployeeAccesses
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id
select new EmployeeWithEmail{
EmpNum = ea.EmpNo ?? 0,
EmailAddress = vh.EmailAddress,
LNameByFname = vh.LNameByFName,
Active2 = ea.Active ?? false
}).ToList();
}
Linq's outer join syntax uses 2 parts. First an into then DefaultIfEmpty
In your case, an outer join might look like this:
(from ea in context.EmployeeAccesses
join vh in context.View_HCM on (Int16)ea.EmpNo equals vh.EmpNo
join rl in context.EmployeeAccessLevels on ea.RoleID equals rl.id into outer_join
from subjoin in outer_join.DefaultIfEmpty()
select new EmployeeWithEmail{
EmpNum = ea.EmpNo ?? 0,
EmailAddress = vh.EmailAddress,
LNameByFname = vh.LNameByFName,
Active2 = ea.Active ?? false
}).ToList();
There are many tutorials on how to create the outer join in LINQ.
I have the following SQL:
select o.tekst as Enhet,
coalesce(f.Antall,0) as AntallF,
coalesce(f.snitt,0) as SnittF,
coalesce(b.antall,0) as AntallB
from tblhandlingsplan hp
inner join tblorg o on hp.eierorgid = o.orgid
left outer join (select f.handlingsplanid, count(t.tiltakid) as Antall, coalesce(avg(convert(float,t.status)),0) as Snitt from tblhandlingsplanforbedring f left outer join tblhandlingsplantiltak t on f.forbedringsid = t.forbedringsid group by f.handlingsplanid) f on hp.handlingsplanid = f.handlingsplanid
left outer join (select b.handlingsplanid, count(b.bevaringsid) as Antall from tblhandlingsplanbevaring b group by b.handlingsplanid) b on hp.handlingsplanid = b.handlingsplanid
where utsendingsid = 1
Which works exactly how I want it... Now I'm trying to convert this to LINQ...
I have gotten this far
from h in TblHandlingsplans
join o in TblOrgs
on h.EierOrgID equals o.OrgID
join f in TblHandlingsplanForbedrings
on h.HandlingsplanID equals f.HandlingsplanID into f2
join b in TblHandlingsplanBevarings
on h.HandlingsplanID equals b.HandlingsplanID into b2
where h.UtsendingsID == 1
select new {
Enhet = o.Tekst,
AntallF = f2.Count(),
AntallB = b2.Count()
}
however now I'm stuck... I can't for the life of me figure out how to include the average part from the SQL solution... Any takers?
I'm thinking of shoving the whole thing into a SP and leave it with that...
var query1 = from a in DB.Table1
select new
{
Id = a.Id,
Average = a.B.Average()
};
var query2 = from b in DB.Table2
join c in query1 on b.Id equals c.Id
select c;
Just freehanded it, so it might not actually work, but is that the kind of thing you're trying to do? That would result in a single SQL query being created when query2 was used.