Linq Expression: Left Join Duplicating Rows - c#

I'm trying to execute this Linq Expression, but the LEFT JOIN is duplicating the rows.
When I write this exactly query in SQL it works ok, but when I write it in Linq Expression the LEFT JOIN duplicates the row.
I've tried to group by.. but I've got the same result.
var sql = (from project in db.Project
join suitT in db.SuitT
on project.Id equals suitT.IdProject
join inspec in db.Inspec
on suitT.Id equals inspec.IdSuitT
join listFinalDef in db.ListFinalDef
on inspec.Id equals listFinalDef.IdInspec
into myListFinalDef
from groupListFinalDef in myListFinalDef.DefaultIfEmpty()
join artefact1 in db.Artefact
on groupListFinalDef.Id equals artefact1.IdListFinalDef
into myArtefact1
from groupArtefact1 in myArtefact1.DefaultIfEmpty()
join artefact2 in db.Artefact
on inspec.Id equals artefact2.IdInspec
into myArtefact2
from groupArtefact2 in myArtefact2.DefaultIfEmpty()
join typeArtefact in db.TypeArtefact
on inspec.IdTypeArtefact equals typeArtefact.Id
where ...
select new ArtefactModels
{
IdArtefact1 = groupArtefact1.Id,
IdArtefact2 = groupArtefact2.Id,
...,
...,
...
}).ToList();
Whats happening: Duplicating the row
Example
Id: 1 | ProjectName: Project 1 | InspecTitle: Title 1
Id: 1 | ProjectName: Project 1 | InspecTitle: Title 1
Id: 2 | ProjectName: Project 2 | InspecTitle: Title 2
Id: 2 | ProjectName: Project 2 | InspecTitle: Title 2
What I'm trying to do:
Example
Id: 1 | ProjectName: Project 1 | InspecTitle: Title 1
Id: 2 | ProjectName: Project 2 | InspecTitle: Title 2
EDITED:
I'm trying to execute this SQL but with LINQ Expression:
SELECT art1.Id AS IdArtefact1, art2.Id AS IdArtefact2, ...
FROM
Project
INNER JOIN SuitT ON Project.Id = SuitT.IdProject
INNER JOIN Inspec ON SuitT.Id = Inspec.IdSuitT
LEFT JOIN ListFinalDef ON Inspec.Id = ListFinalDef.IdInspec
LEFT JOIN Artefact AS art1 ON ListFinalDef.Id = art1.IdListFinalDef
LEFT JOIN Artefact AS art2 ON Inspec.Id = art2.IdInspec
INNER JOIN TypeArtefact ON Inspec.IdTypeArtefact = TypeArtefact.Id
WHERE ...

The groupby goes like this:
var sql = (from project in db.Project
join suitT in db.SuitT
on project.Id equals suitT.IdProject
join inspec in db.Inspec
on suitT.Id equals inspec.IdSuitT
join listFinalDef in db.ListFinalDef
on inspec.Id equals listFinalDef.IdInspec
into myListFinalDef
from groupListFinalDef in myListFinalDef.DefaultIfEmpty()
join artefact1 in db.Artefact
on groupListFinalDef.Id equals artefact1.IdListFinalDef
into myArtefact1
from groupArtefact1 in myArtefact1.DefaultIfEmpty()
join artefact2 in db.Artefato
on inspec.Id equals artefact2.IdInspec
into myArtefact2
from groupArtefact2 in myArtefact2.DefaultIfEmpty()
join typeArtefact in db.TypeArtefact
on inspec.IdTypeArtefact equals typeArtefact.Id
where ...
select new ArtefactModels
{
IdArtefact1 = groupArtefact1.Id,
IdArtefact2 = groupArtefact2.Id,
...,
...,
...
})
//This will return IGroupable<key,value> with as key IdArtefact and as
//value an Enumerable of all the anonymous types with the same IdArtefact
.GroupBy(a => a.IdArtefact1)
//Select first value per IdArtefact if there's multiple
.Select(a => a.FirstOrDefault())
.ToList();

You can use my library PowerfulExtensions. It has a Distinct by properties method documented here.
Just call it at the end of the LINQ chain and pass the properties that make a row distinctive.

Related

How to filter many to many joins in EF Core

I have the structure below:
Product Category
Product (with one Product Category)
ProductComplementCategory (Product has many ProductComplementCategory)
ComplementCategory (ProductComplementCategory has one ComplementCategory)
ComplementCategoryComplements (ComplementCategory has many ComplementCategoryComplements)
Complement (ComplementCategoryComplements has one Complement)
All tables has the property "Active" and I need to select all tables with join but filtering active = 1 in tables Product Category, Product, ComplementCategory and Complement
Query in SQL:
SELECT
pc.*,
p.*,
cc.*,
c.*
FROM
ProductCategory pc
JOIN Product p ON pc.[ Uid ] = p.ProductCategoryId
JOIN ProductComplementCategory pcc ON p.[ Uid ] = pcc.ProductID
JOIN ComplementCategory cc ON pcc.ComplementCategoryID = cc.[ Uid ]
JOIN ComplementCategoryComplements ccc ON cc.[ Uid ] = ccc.ComplementCategoryID
JOIN Complement c ON ccc.ComplementID = c.[ Uid ]
WHERE
pc.Active = 1
AND p.Active = 1
AND cc.Active = 1
AND c.Active = 1
I need to do this query in EF Core!
i hope this would help you
var temp = (
from pc in ProductCategory
join p in Product
on pc.Uid equals p.ProductCategoryId
join pcc in ProductComplementCategory
on pcc on p.Uid equals pcc.ProductID
join permission in ComplementCategory
on pcc.ComplementCategoryID equals cc.Uid
join cc in ComplementCategoryComplements
on cc.Uid equals ccc.ComplementCategoryID
join c in Complement
on ccc.ComplementID equals c.Uid
where pc.Active = 1 && p.Active = 1 && cc.Active = 1 && c.Active = 1
select new { pc,p,pcc,c,cc }
).ToList

LINQ giving difference results to SQL

I have been tasked with replacing SQL query with a LINQ query and in the main I get the data that I would expect, however I think there is a join that has gone wrong somewhere and I'm not sure how or where as most of the time I avoid EF where I can in favour of dapper.
The SQL I have been given
SELECT
SFM.FieldId,
QSRA.Answer,
SFM.FieldNo
FROM
[forms].QS
INNER JOIN
[sessions].QSR
ON QSR.QSNo = QS.QSNo
INNER JOIN
(
SELECT
MAX(QS.QSVersion) AS LatestVersion
FROM
[forms].QS
INNER JOIN
[sessions].QSR
ON QSR.QSNo = QS.QSNo
WHERE
QSR.QsrId = #QSRID
AND QS.StatusId = 2
) AS QSLatestVer
ON QS.QSVersion = QSLatestVer.LatestVersion
INNER JOIN
[forms].QSSectionMappings QSM
ON QSM.QSId = QS.QSId
INNER JOIN
[forms].SectionFieldMappings SFM
ON SFM.SectionId = QSM.SectionId
INNER JOIN
[sessions].QSRAnswers QSRA
ON (
QSRA.QsrId = QSR.QsrId
AND QSRA.FieldNo = SFM.FieldNo
)
WHERE
QSR.QsrId = #QSRID;
The LINQ I have used to replace it with and then am going to look at refining.
var results = (from qs in QS
join qsr in QSRs on qs.QSNo equals qsr.QSNo
join qsm in QSSectionMappings on qs.QSId equals qsm.QSId
join sfm in SectionFieldMappings on qsm.SectionId equals sfm.SectionId
join qsra in QSRAnswers on qsr.QsrId equals qsra.QsrId
join sub in (from subQs in QS
join subQsr in QSRs on subQs.QSNo equals subQsr.QSNo
where subQs.StatusId == 2 && subQsr.QsrId == Guid.Parse(qsrIdGuid)
select subQs.QSVersion
) on qs.QSVersion equals sub
where qsr.QsrId == Guid.Parse(qsrIdGuid)
group new
{
FieldId = sfm.FieldId,
Answer = qsra.Answer,
FieldNo = decimal.Parse(sfm.FieldNo),
} by new
{
FieldId = sfm.FieldId,
Answer = qsra.Answer,
FieldNo = sfm.FieldNo
} into g
select new
{
FieldId = g.Key.FieldId,
Answer = g.Key.Answer,
FieldNo = g.Key.FieldNo,
}
);
The results I get with the SQL are
FieldId |Answer |FieldNo
40D10975-AF2E-4518-AC35-08D7C70E1BF9 |3/17/2020 12:00:00 AM |1
71A95FD5-08E0-4201-AC36-08D7C70E1BF9 |3/25/2020 12:00:00 AM |2
The results I get with LINQ are
FieldId |Answer |FieldNo
40d10975-af2e-4518-ac35-08d7c70e1bf9 |3/17/2020 12:00:00 AM |1
--Correct
40d10975-af2e-4518-ac35-08d7c70e1bf9 |3/25/2020 12:00:00 AM |1 --Wrong
71a95fd5-08e0-4201-ac36-08d7c70e1bf9 |3/17/2020 12:00:00 AM |2 --Wrong
71a95fd5-08e0-4201-ac36-08d7c70e1bf9 |3/25/2020 12:00:00 AM |2
--Correct
I would appreciate if you could let me know where I am going wrong in the join
The results are the same with the nested select, and the grouping as without the grouping.
In the SQL you have the following for joining the QSRAnwers table
INNER JOIN
[sessions].QSRAnswers QSRA
ON (
QSRA.QsrId = QSR.QsrId
AND QSRA.FieldNo = SFM.FieldNo
)
However in the Linq code you have
join qsra in QSRAnswers on qsr.QsrId equals qsra.QsrId
So you're missing the FieldNo comparison for that join. Just change it to
join qsra in QSRAnswers
on new{qsr.QsrId, sfm.FieldNo} equals new{qsra.QsrId, qsra.FieldNo}
To get the same functionality.

MS SQL Querying Tables Where Data may or may not exist

Have not come across an exact use-case for what I'm trying to achieve on Stackoverflow so will explain it in the hopes someone can assist.
I have two tables, one contains a master activity list and the other contains who completed those activities.
Table A is the Activity Table. This is a distinct list of things that can be done.
ID | Activity
---------------------------
1 | Change Oil
2 | Change Airfilter
3 | Change Brake Fluid
Table B is the Activity Log table. This tracks where people have done one of the above Activities. ActivityID links to ID on Table A.
ID | ActivityID | CompletedBy
---------------------------------------
1 | 1 | john#auto.com
2 | 1 | sally#auto.com
3 | 3 | john#auto.com
What I am trying to produce is a list of all activities, but then only for a distinct person. I have tried multiple ways for this, but can only get the query to show where values exist in both tables.
My preferred output would be the following, where in the query i have asked to show me the full Activity list and also to show where John has completed anything. If there is no record in Table B for this activity, to show a blank value.
ID | Activity | CompletedBy
-------------------------------------------
1 | Change Oil | john#auto.com
2 | Change Airfilter |
3 | Change Brake Fluid | john#auto.com
Here is my current SQL Query in my attempt to work this one out, which right now the results only return ID 1 and 3 from that example above, where john actually has a record in Table B.
Select a.ID, a.Activity, b.CompletedBy
FROM ActivityList a
LEFT OUTER JOIN ActivityLog b
ON a.ID = b.ActivityID
Where CompletedBy = 'john#auto.com'
GROUP BY a.ID, a.Activity, b.CompletedBy
Any help or pointers in the right direction would be greatly appreciated.
If you move the CompletedBy restriction into the join conditions you would not need the group by.
Select a.ID, a.Activity, b.CompletedBy
FROM ActivityList a
LEFT OUTER JOIN ActivityLog b
ON a.ID = b.ActivityID and b.CompletedBy = 'john#auto.com'
Change your where clause to Where CompletedBy = 'john#auto.com' or CompletedBy is null so it matches on a text match or the case where the name does not exist at all. You can also use isnull( to convert the null value to a empty string if that is the behavior you are wanting.
Select a.ID, a.Activity, isnull(b.CompletedBy, '') as CompletedBy
FROM ActivityList a
LEFT OUTER JOIN ActivityLog b
ON a.ID = b.ActivityID
Where CompletedBy = 'john#auto.com' or b.CompletedBy is null
GROUP BY a.ID, a.Activity, b.CompletedBy
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable table_A = new DataTable();
table_A.Columns.Add("ID", typeof(int));
table_A.Columns.Add("Activity", typeof(string));
table_A.Rows.Add(new object[] { 1, "Change Oil"});
table_A.Rows.Add(new object[] { 2, "Change Airfilter"});
table_A.Rows.Add(new object[] { 3, "Change Brake Fluid"});
DataTable table_B = new DataTable();
table_B.Columns.Add("ID", typeof(int));
table_B.Columns.Add("ActivityID", typeof(string));
table_B.Columns.Add("CompletedBy", typeof(string));
table_B.Rows.Add(new object[] { 1, 1, "john#auto.com"});
table_B.Rows.Add(new object[] { 2, 1, "sally#auto.com"});
table_B.Rows.Add(new object[] { 3, 3, "john#auto.com"});
var groups = (from a in table_A.AsEnumerable()
join b in table_B.AsEnumerable() on a.Field<int>("ID") equals b.Field<int>("ID")
select new { a = a, b = b})
.GroupBy(x => x.b.Field<string>("CompletedBy"))
.ToList();
}
}
}
----------
Select a.ID, a.Activity, b.CompletedBy
FROM ActivityList a
Right OUTER JOIN ActivityLog b
ON a.ID = b.ActivityID
Where CompletedBy = 'john#auto.com'
GROUP BY a.ID, a.Activity, b.CompletedBy
Your query is correct just change the potion of condition.
Select a.ID, a.Activity, b.CompletedBy
FROM ActivityList a
LEFT JOIN ActivityLog b
ON a.ID = b.ActivityID AND b.CompletedBy = 'john#auto.com'
GROUP BY a.ID, a.Activity, b.CompletedBy

How to convert SQL Left Join query to Linq to Entity in C#?

I have a Table: Material(ID,Name,MAterialParentID)
SELECT c1.ID,c1.Name as ParentName,c2.id,c2.Name
FROM Material c1 Left JOIN Material c2
ON c1.ID = c2.MaterialParentID
ID ParentName id Name
1 Aluminium 2 Cavity
1 Aluminium 3 Copper
1 Aluminium 4 Flooring
2 Cavity NULL NULL
3 Copper NULL NULL
4 Flooring NULL NULL
5 Glass NULL NULL
I want to convert the above SQL Query to Linq Query using Liq to Entities.
Help Appreciated!
if the table is only for reading you could simply create a view and then when using reverse engineering make sure you have views imported.
or if you did want this done in LINQ here is the MSDN example
var innerJoinQuery =
from cust in customers
join dist in distributors on cust.City equals dist.City
select new { CustomerName = cust.Name, DistributorName = dist.Name };
this is how yours would look
var Material = from M in db.Materials
join M2 in db.Materials on M.ID equals M2.MaterialParentID
select new {ParentID = M.ID, ParentName = M.Name, M2.ID, M2.Name };
i have edited my post above as you can see i have included the ParentID to make all columns unique
For this kind of problem linqpad is your friend.
I'd suggest something like:
var materials = (from m in context.Material
let moreMaterials = (from m2 in context.Material where m2.id == m.id select m2).FirstOrDefault()
select m).ToList();
But you can use linqpad to customise to your query requirements.

Left Join If A Certain Field Is A Certain Value?

I'm having a difficult time building a left join query that does something like this:
var Results =
from a in Db.Table1
join b in Db.Table2 on a.Id equals b.Id into c //IF b.Value2 == 1
from d in c.DefaultIfEmpty()
select new {Table1 = a};
The problem I'm having is that I simply can't add "where b.Value2==1" before the "select" because this will exclude all joined rows with b.Value2 != 1.
What I want to have happen is for the columns to join only if b.Value2==1, and if not, it will keep the row (instead of excluding them as the where described above would), but just not join the two columns.
I hope this makes sense and there is a way to do this, thanks!
Edit:
Table1
ValueA=1,ValueB=1
ValueA=2,ValueB=2
Table2
ValueB=1,ValueC=1
ValueB=2,ValueC=2
Desired Result (joining on Table1.ValueB==Table2.ValueB Where Table2.ValueC == 2, still include this row, but just don't join it to Table2):
ValueA=1, ValueB=1, ValueC=null //ValueC=null because where Table2.ValueB==1,ValueC != 2
ValueA=2, ValueB=2, ValueC=2
This should work, basically create a dummy var and join on it. The problem is this doesn't make for a friendly push down operation to the DB.
var Results =
from a in Db.Table1
join b in Db.Table2 on
new { ID= a.Id, Bit = true }
equals
new { ID = b.Id, Bit = b.Value ==1 }
into c
from d in c.DefaultIfEmpty()
select new {
Table1 = a,
Table2= d //will be null if b.Value is not 1
};
add the where, then add another query that's just for b.Value2 != 1, merge both queries?
Create two linq queries, which will be composed into one sql query:
The first query will do a regular outer join
var partResult= from a in Db.Table1
join b in Db.Table2 on a.Id equals b.Id into c
from d in c.DefaultIfEmpty()
select new { Table1 = a, Table2=d};
The second query will select all entries from the first query where Table2 is null or Table2.Id==1
var result = from obj in partResult
where (obj.Table2 == null || obj.Table2.Id == 2)
select obj.Table1;
This will be composed into a single select statement when the queries execute:
SELECT [t0].[Id]
FROM [dbo].[Table1] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[Id]
FROM [dbo].[Table2] AS [t1]
) AS [t2] ON [t0].[Id] = [t2].[Id]
WHERE ([t2].[test] IS NULL) OR ([t2].[Id] = 2)
if Table 1 has Ids 1, 2, 3, 4, 5, 6 and Table2 has Ids 1, 2, 3, 4 the result will be 1,5,6

Categories

Resources