Complex joins in Linq with multiple tables and LEFT OUTER JOIN - c#

Hoping someone can point me in the right direction with this join. I'm trying to convert some SQL to Linq. My SQL has a left outer join after several inner joins. The following SQL produces the desired result:
SELECT TOP(50) [t].[TagFriendlyName] AS [TagName], [t0].[timeStamp] AS [LastSeen], [l].[Name] AS [LocationName]
FROM [Tags] AS [t]
INNER JOIN [tag_reads] AS [t0] ON [t].[epc] = [t0].[epc]
INNER JOIN [ReaderData] AS [r] ON [t0].[ReaderDataId] = [r].[Id]
LEFT OUTER JOIN [Readers] AS [r0] ON [r].[mac_address] = [r0].[mac_address]
INNER JOIN [Locations] AS [l] on [t0].[antennaPort] = [l].[AntennaId] AND [r].[Id] = [l].[ReaderId]
GROUP BY [t].[TagFriendlyName], [t0].[timeStamp], [l].[Name]
ORDER BY [t0].[timeStamp] DESC
My Linq code is as follows, but I can't figure out how to get the left outer join inserted properly. Not sure how to introduce the Readers table that needs the LEFT OUTER JOIN:
var query = (
from tags in db.Tags
join tagreads in db.tag_reads on tags.epc equals tagreads.epc
join readerdata in db.ReaderData on tagreads.ReaderDataId equals readerdata.Id
join readers in db.Readers on readerdata.mac_address equals readers.mac_address
group tags by new { tags.TagFriendlyName, timestamp = tagreads.timeStamp, readerdata.mac_address } into grp
select new CurrentStatus()
{
TagName = grp.Key.TagFriendlyName,
LastSeen = grp.Key.timestamp,
LocationName = grp.Key.mac_address
}
)
.OrderByDescending(o => o.LastSeen)
According to the documentation I need to use DefaultIfEmpty(), but I'm not sure where to introduce the Readers table.
Using EF Core 3.1.0. THANKS!

You should apply Left Join this way:
join readers in db.Readers on readerdata.mac_address equals readers.mac_address into readersJ
from readers in readersJ.DefaultIfEmpty()
The full code:
var query = (
from tags in db.Tags
join tagreads in db.tag_reads on tags.epc equals tagreads.epc
join readerdata in db.ReaderData on tagreads.ReaderDataId equals readerdata.Id
join readers in db.Readers on readerdata.mac_address equals readers.mac_address into readersJ
from readers in readersJ.DefaultIfEmpty()
join locations in db.Locations
on new { ap = tagreads.antennaPort, rd = readerdata.Id }
equals new { ap = locations.AntennaId, rd = locations.ReaderId }
group tags by new { tags.TagFriendlyName, timestamp = tagreads.timeStamp, readerdata.mac_address } into grp
select new CurrentStatus()
{
TagName = grp.Key.TagFriendlyName,
LastSeen = grp.Key.timestamp,
LocationName = grp.Key.mac_address
}
)
.OrderByDescending(o => o.LastSeen)

Related

left join failing in query with multiple joins

Before this gets called a duplicate, I have looked around on SO and found a way to do this but it isn't working.
My query is
var GetAllProjects = from f in dc.vw_gmi_all_projects
join mc in dc.gmi_maintenance_classes on f.maintenance_classID equals mc.maintenance_classID
join ms in dc.gmi_maintenance_subclasses on f.maintenance_subclassID equals ms.maintenance_subclassID
join pm in dc.master_project_milestones on f.pmID equals pm.pmID
join ac in dc.vw_master_Countries on f.country_display_name equals ac.country_display_name
join pd in dc.gmi_project_details on f.project_dataID equals pd.project_dataID
join md in dc.vw2_master_districts on f.country_display_name equals md.element_display_name
join ml in dc.vw2_master_lmus on pd.dataID equals ml.elementID into gl from sub in gl.DefaultIfEmpty()
where (mc.maintenance_classID == 3 && ms.maintenance_subclassID != 11)
select new
{
f.project_dataID,
f.projectID,
f.project_title,
f.local_projectID,
f.pm_display_name,
f.reu_name,
f.reuID,
f.sectorID,
f.sector_display_name,
f.country_display_name,
f.maintenance_classID,
f.maintenance_subclassID,
mc.maintenance_class_display_name,
ms.maintenance_subclass_display_name,
pm.pm_name,
ac.region_display_name,
pd.dataID,
md.element_display_name,
ac.cluster_display_name,
display_name = sub.element_display_name
};
foreach (var a in GetAllProjects)
{
lst.Add(new ReportFilter
{
project_dataID = (int)a.project_dataID,
projectID = a.projectID,
project_title = a.project_title,
local_projectID = a.local_projectID,
pm_display_name = a.pm_display_name,
reu_name = a.reu_name,
reuID = a.reuID,
country_display_name = a.country_display_name,
sectorID = a.sectorID,
sector_display_name = a.sector_display_name,
maintenance_classID = a.maintenance_classID,
maintenance_subclassID = a.maintenance_subclassID,
maintenance_class_display_name = a.maintenance_class_display_name,
maintenance_subclass_display = a.maintenance_subclass_display_name,
pm_name = a.pm_name,
region_display_name = a.region_display_name,
dataID = a.dataID,
district = a.element_display_name,
cluster_display_name = a.cluster_display_name
});
}
This is where the left join is supposed to take place..
join ml in dc.vw2_master_lmus on pd.dataID equals ml.elementID into gl from sub in gl.DefaultIfEmpty()
This query runs fine if I leave out the attempt at left join and leave that join out entirely, but I need to get the left join to work so I can get the rest of the records. A typical join won't work like the others because it doesn't return any records.
So where am I going wrong with my query, or doing wrong? I know where its going wrong just not sure how to fix it.
Thanks
More Details
This query creates a list and then I query against this list. However, this query won't return any records because of my attempt at creating a left join.
EDIT
Here is the SQL that I wrote and trying to recreate it using Linq
select * from [vw_gmi_all_projects] f
inner join [gmi_maintenance_classes] mc on f.maintenance_classID = mc.maintenance_classID
inner join [gmi_maintenance_subclasses] ms on f.maintenance_subclassID = ms.maintenance_subclassID
inner join [master_project_milestones] pm on f.pmID = pm.pmID
inner join [vw_master_Countries] ac on f.country_display_name = ac.country_display_name
inner join [gmi_project_details] pd on f.project_dataID = pd.project_dataID
inner join [vw2_master_district] md on f.country_display_name = md.element_display_name
left join [vw2_master_lmu] ml on pd.dataID = ml.elementID
where (mc.maintenance_classID = 3 and ms.maintenance_subclassID != 11)
I hope this helps.
Try the syntax from this answer instead: https://stackoverflow.com/a/4739738/1869660
I think that's an easier syntax when creating LEFT JOINs (note: from, not join).
var GetAllProjects = from f in dc.vw_gmi_all_projects
join mc in dc...
join ms in dc...
join pm in dc...
join ac in dc...
join pd in dc...
join md in dc...
from ml in dc.vw2_master_lmus.Where(ml => ml.elementID == pd.dataID).DefaultIfEmpty()
where ...

Left outer join and multiple counts SQL to LINQ

How would this query using an inner join, left outer join, group by and two counts be converted to linq?
SELECT
c.EndowmentID,
COUNT(DISTINCT f.CriterionID) AS RequiredCriteria,
COUNT(r.ChoiceID) AS Response
FROM
Criteria c
INNER JOIN
Filters f
ON
c.ID = f.CriterionID
LEFT OUTER JOIN
Responses r
ON
f.ChoiceID = r.ChoiceID
WHERE
f.IsRequirement = 1
GROUP BY
c.EndowmentID;
This is what I have done so far:
var result =
from c in context.Criteria
join f in context.Filters on c.ID equals f.CriterionID
join r in context.Responses on f.ChoiceID equals r.ChoiceID into resfil
from rf in resfil.DefaultIfEmpty()
group rf by c.EndowmentID into grouped
select new
{
EndowmentID = grouped.Key,
Requirements = grouped.Count(t=>t.CriterionID),
Response = grouped.Count(t=>t.ChoiceID)
};
You need to group using an anonymous class. This will allow you to access all your tables in your select statement
group new { c, f, rf } by c.EndowmentID into grouped
SQL: COUNT(DISTINCT f.CriterionID) AS RequiredCriteria,
This can be written by first selecting the f.CriterionID column, Distinct(), Count()
RequiredCriteria = grouped.Select(x => x.f.CriterionID).Distinct().Count()
SQL: COUNT(r.ChoiceID)
Response = grouped.Select(x => x.rf.ChoiceID).Count()

LINQ multiple joins with one left join

I am fairly new to LINQ and I am struggling to make a multiple JOIN.
So, this is how my database structure looks like:
Now, how should my query look like, if I have a particular Grade and I want to select
{Student.IndexNo, GradeValue.Value}, but if there is no grade value for a particular grade and particular user, null should be returned (Left join)?
The trick to get a LEFT join is to use the DefaultIfEmpty() method:
var otherValue = 5;
var deps = from tbl1 in Table1
join tbl2 in Table2
on tbl1.Key equals tbl2.Key into joinGroup
from j in joinGroup.DefaultIfEmpty()
where
j.SomeProperty == "Some Value"
&& tbl1.OtherProperty == otherValue
select j;
Deliberately posting this in 2015 for newbies looking for solution on google hits. I managed to hack and slash programming my way into solution.
var projDetails = from r in entities.ProjekRumah
join d in entities.StateDistricts on r.ProjekLocationID equals d.DistrictID
join j in entities.ProjekJenis on r.ProjekTypeID equals j.TypeID
join s in entities.ProjekStatus on r.ProjekStatusID equals s.StatusID
join approvalDetails in entities.ProjekApproval on r.ProjekID equals approvalDetails.ProjekID into approvalDetailsGroup
from a in approvalDetailsGroup.DefaultIfEmpty()
select new ProjectDetailsDTO()
{
ProjekID = r.ProjekID,
ProjekName = r.ProjekName,
ProjekDistrictName = d.DistrictName,
ProjekTypeName = j.TypeName,
ProjekStatusName = s.StatusName,
IsApprovalAccepted = a.IsApprovalAccepted ? "Approved" : "Draft",
ProjekApprovalRemarks = a.ApprovalRemarks
};
Produces following SQL code internally
{SELECT [Extent1].[ProjekID] AS [ProjekID]
,[Extent1].[ProjekName] AS [ProjekName]
,[Extent2].[DistrictName] AS [DistrictName]
,[Extent3].[TypeName] AS [TypeName]
,[Extent4].[StatusName] AS [StatusName]
,CASE
WHEN ([Extent5].[IsApprovalAccepted] = 1)
THEN N'Approved'
ELSE N'Draft'
END AS [C1]
,[Extent5].[ApprovalRemarks] AS [ApprovalRemarks]
FROM [dbo].[ProjekRumah] AS [Extent1]
INNER JOIN [dbo].[StateDistricts] AS [Extent2] ON [Extent1].[ProjekLocationID] = [Extent2].[DistrictID]
INNER JOIN [dbo].[ProjekJenis] AS [Extent3] ON [Extent1].[ProjekTypeID] = [Extent3].[TypeID]
INNER JOIN [dbo].[ProjekStatus] AS [Extent4] ON [Extent1].[ProjekStatusID] = [Extent4].[StatusID]
LEFT JOIN [dbo].[ProjekApproval] AS [Extent5] ON [Extent1].[ProjekID] = [Extent5].[ProjekID]
}

Queries generated by group by vs group join

I have the following group by linq statement
from c in Categories
join p in Products on c equals p.Category into ps
select new { Category = new {c.CategoryID, c.CategoryName}, Products = ps };
However this generates the following left outer join query and returns all categories even if there are no products associated.
SELECT [t0].[CategoryID], [t0].[CategoryName], [t1].[ProductID], [t1].[ProductName], [t1].[SupplierID], [t1].[CategoryID] AS [CategoryID2], [t1].[QuantityPerUnit], [t1].[UnitPrice], [t1].[UnitsInStock], [t1].[UnitsOnOrder], [t1].[ReorderLevel], [t1].[Discontinued], (
SELECT COUNT(*)
FROM [Products] AS [t2]
WHERE [t0].[CategoryID] = [t2].[CategoryID]
) AS [value]
FROM [Categories] AS [t0]
LEFT OUTER JOIN [Products] AS [t1] ON [t0].[CategoryID] = [t1].[CategoryID]
ORDER BY [t0].[CategoryID], [t1].[ProductID]
What I really want is to return only those categories that have associated products. But if I re-write the linq query like so:
from c in Categories
join p in Products on c equals p.Category
group p by new {c.CategoryID, c.CategoryName} into ps
select new { Category = ps.Key, Products = ps };
This gives me the desired result but a query is generated for each category:
SELECT [t0].[CategoryID], [t0].[CategoryName]
FROM [Categories] AS [t0]
INNER JOIN [Products] AS [t1] ON [t0].[CategoryID] = [t1].[CategoryID]
GROUP BY [t0].[CategoryID], [t0].[CategoryName]
GO
-- Region Parameters
DECLARE #x1 Int SET #x1 = 1
DECLARE #x2 NVarChar(9) SET #x2 = 'Beverages'
-- EndRegion
SELECT [t1].[ProductID], [t1].[ProductName], [t1].[SupplierID], [t1].[CategoryID], [t1].[QuantityPerUnit], [t1].[UnitPrice], [t1].[UnitsInStock], [t1].[UnitsOnOrder], [t1].[ReorderLevel], [t1].[Discontinued]
FROM [Categories] AS [t0]
INNER JOIN [Products] AS [t1] ON [t0].[CategoryID] = [t1].[CategoryID]
WHERE (#x1 = [t0].[CategoryID]) AND (#x2 = [t0].[CategoryName])
GO
-- Region Parameters
DECLARE #x1 Int SET #x1 = 2
DECLARE #x2 NVarChar(10) SET #x2 = 'Condiments'
-- EndRegion
SELECT [t1].[ProductID], [t1].[ProductName], [t1].[SupplierID], [t1].[CategoryID], [t1].[QuantityPerUnit], [t1].[UnitPrice], [t1].[UnitsInStock], [t1].[UnitsOnOrder], [t1].[ReorderLevel], [t1].[Discontinued]
FROM [Categories] AS [t0]
INNER JOIN [Products] AS [t1] ON [t0].[CategoryID] = [t1].[CategoryID]
WHERE (#x1 = [t0].[CategoryID]) AND (#x2 = [t0].[CategoryName])
GO
...
Is there a way to do the equivalent of a inner join and group by and still only produce a single query like the group join?
var queryYouWant =
from c in Categories
join p in Products on c equals p.Category
select new {Category = c, Product = p};
var result =
from x in queryYouWant.AsEnumerable()
group x.Product by x.Category into g
select new { Category = g.Key, Products = g };
Is there a way to do the equivalent of a inner join and group by and still only produce a single query like the group join?
No. When you say GroupBy followed by non-aggregated access of the group elements, that's a repeated query with the group key as a filter.
What is the purpose of that join?
Your original query is identical to this:
from c in Categories
select new { Category = new { c.CategoryID, c.CategoryName }, c.Products }
Am I somehow missing something obvious???
If you want only categories with products, then do this:
from c in Categories
where c.Products.Any()
select new { Category = new { c.CategoryID, c.CategoryName }, c.Products }
Or, if you want to flatten the results:
from p in Products
select new { p, p.Category.CategoryID, p.Category.CategoryName }
The latter will translate into an inner or outer join - depending on whether that relationship is nullable. You can force the equivalent of an inner join as follows:
from p in Products
where p.Category != null
select new { p, p.Category.CategoryID, p.Category.CategoryName }

nHibernate complex join

I have a need to perform a complex left join on a table and am unsure how to write the code using a criteria query. Currently I have:
public IList<RezolutionConfig> GetSearchConfigByManagerCategoryProduct(int ManagerId, int ProductTypeId, int ConfigCategoryId)
{
ICriteria criteria = Session.GetISession().CreateCriteria(typeof(RezolutionConfig))
.CreateAlias("RezolutionConfigCategory", "rcc")
.Add(Expression.Eq("rcc.id", ConfigCategoryId))
.CreateAlias("RezolutionProductType","rpt")
.Add(Expression.Eq("rpt.id", ProductTypeId))
.CreateAlias("RezolutionManagerConfigs", "rmc", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.CreateAlias("rmc.Manager", "m", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
.Add(Expression.Eq("m.id", ManagerId));
return criteria.List<RezolutionConfig>();
}
which produces:
SELECT *
FROM [dbo].[RezolutionConfig] this_
inner join [dbo].[RezolutionConfigCategory] rcc1_ on this_.[RezolutionConfigCategoryId]=rcc1_.[RezolutionConfigCategoryId]
inner join [dbo].[RezolutionProductType] rpt2_ on this_.[RezolutionProductTypeId]=rpt2_.[RezolutionProductTypeId]
left outer join [dbo].[RezolutionManagerConfig] rmc3_ on this_.[RezolutionConfigID]=rmc3_.[RezolutionConfigID]
left outer join [dbo].[Manager] m4_ on rmc3_.[ManagerID]=m4_.[ManagerID] WHERE rcc1_.[RezolutionConfigCategoryId] = 1
and rpt2_.[RezolutionProductTypeId] = 1
and m4_.[ManagerID] = 9135
What I need to produce is
SELECT *
FROM [dbo].[RezolutionConfig] this_
inner join [dbo].[RezolutionConfigCategory] rcc1_ on this_.[RezolutionConfigCategoryId]=rcc1_.[RezolutionConfigCategoryId]
inner join [dbo].[RezolutionProductType] rpt2_ on this_.[RezolutionProductTypeId]=rpt2_.[RezolutionProductTypeId]
left outer join [dbo].[RezolutionManagerConfig] rmc3_ on this_.[RezolutionConfigID]=rmc3_.[RezolutionConfigID]
left outer join [dbo].[Manager] m4_ on rmc3_.[ManagerID]=m4_.[ManagerID] and m4_.[ManagerID] = 9135
WHERE rcc1_.[RezolutionConfigCategoryId] = 1
and rpt2_.[RezolutionProductTypeId] = 1
NHibernate doesn't support adding a constraint to a join as you would like: left outer join [dbo].[Manager] m4_ on rmc3_.[ManagerID]=m4_.[ManagerID] and m4_.[ManagerID] = 9135. I think your best approach will be to write the query in HQL and use a subquery to constrain Manager.

Categories

Resources