Entity Framework / LINQ: Left join defaultifempty fails - c#

I need to join 5 tables in a query. 3 of these tables must have relations, but two of them are optionally connected to an entry.
Because of this, I am trying to do a LEFT JOIN to Table4 and Table5 like this:
var cDesc = (cDesc == null ? "" : cDesc);
var cStreet = (cStreet == null ? "" : cStreet);
var q = await (from t1 in MyContext.Table1
join t2 in MyContext.Table2
on t1.ID equals t2.ObjectID
join t3 in MyContext.Table3
on t2.TeamID equals t3.TeamID
join t4 in MyContext.Table4
on t1.ID equals t4.ObjectID
into join3
from j3 in join3.DefaultIfEmpty()
join t5 in MyContext.Table5
on j3.StorageID equals t5.StorageID
where t2.ObjectType.Equals(16)
&& t3.UserID.Equals(userID)
&& t1.Description.Contains(cDesc)
&& l.Address.Contains(cStreet)
orderby t1.ID descending
select new Table1ListModel
{
ID = t1.ID,
Description = t1.Description,
Address = t5.Address
}
)
.Take(takeThis)
.ToListAsync();
But this query only works for rows that has a connection to Table4, so I'm doing something wrong obviously.
Am I doing the join correctly? Or is the problem that I want to run a where on address that comes from the fifth table?

Basically once you left join one table into a query any additional tables that you want to join to that one should almost always also be done with left joins. In your case you're saying you want keep rows in Table1 that don't have a match in Table4, but then you say you only want matches between Table4 and Table5 which basically will remove all the Table1 results that didn't have a match in Table4. Basically you want something like this
from j3 in join3.DefaultIfEmpty()
join temp5 in MyContext.Table5
on j3.StorageID equals temp5.StorageID into join4
from t5 in join4.DefaultIfEmpty()

This looks like a source of your problem:
join t4 in MyContext.Table4
on t1.ID equals t4.ObjectID
into join3
This means that you are inner joining Table4 to Table1

Related

Incorrect results from multiple joins in Linq2SQL

I've been tasked with converting some crusty old SQL to Linq2SQL and I know this shouldnt be the first choice however it needs to be done.
Problem
I'm stuck attempting to force specific joins to replicate the desired output and the following is a simplified version of the SQL with one parameter variant shown followed by a simplified version of my attempt in Linq2SQL. Several alternative fields may be queried in Table1 hence the use q and q2 in the converted code though the actual parameters are not relevant to the issue.
Aim
The query needs to retrieve relevant rows from Table1, the latest related row from Table2 and additional data from Table2's parent table Table3, preferably in a single pass.
There may be 0-n matches in Table1, Table1 has a 1:n relationship with Table2 with 0-n rows, there is a 1:n relationship between Table3 and Table2.
Regardless of how I structure the expressions, the linq generates an INNER JOIN onto Table2 excluding rows in Table1, how can I structure the linq query to achieve the desired result?
SELECT [...]
FROM Table1 t1
LEFT JOIN (
SELECT MAX(id) AS id, parent_id
FROM Table2
GROUP BY parent_id
) x2 ON t1.id = x2.parent_id
LEFT JOIN Table2 t2 ON x2.id = t2.id
LEFT JOIN Table3 t3 ON t2.table3_id = t3.id
WHERE t1.id = row_id
var q = dc.Table1.AsQueryable();
q = from r in q where r.id == row_id select r;
var q2 = from r in (q)
join x2 in (from r in dc.Table2.DefaultIfEmpty() group r by r.parent_id into maxt2 let max_id = maxt2.Max(f => f.id) select new { maxt2.Key, max_id }) on r.id equals wx.Key
join t2 in dc.Table2.DefaultIfEmpty() on x2.max_id equals t2.id
join t3 in dc.Table3.DefaultIfEmpty() on t2.table3_id equals t3.id
select
{
[...]
};
Here is my translation using my recipe rules:
var Q1 = from t2 in dc.Table2
group t2 by t2.parent_id into t2g
select new { parent_id = t2g.Key, id = t2g.Max(t2 => t2.id) };
var Q2 = from t1 in Table1
where t1.id == row_id
join q1 in Q1 on t1.id equals q1.parent_id into q1j
from q1 in q1j.DefaultIfEmpty()
join t2 in dc.Table2 on q1.id equals t2.id into t2j
from t2 in t2j.DefaultIfEmpty()
join t3 in dc.Table3 on t2.table3_id equals t3.id into t3j
from t3 in t3j.DefaultIfEmpty()
select new { t1, t2, t3 };

What's wrong with the joins in this LINQ query?

I'm trying to replicate the following SQL query in LINQ:
SELECT *
FROM Table1 AS D INNER JOIN Table2 AS DV ON D.Table1Id = DV.Table1Id
INNER JOIN Table3 AS VT ON DV.Table3Id = VT.Table3Id
INNER JOIN Table4 AS C ON DV.CurrencyId = C.CurrencyId
INNER JOIN Table5 AS FP ON DV.DVDate BETWEEN FP.StartDate AND FP.EndDate
INNER JOIN Table6 AS FX ON DV.CurrencyId = FX.FromCurrencyId AND FX.ToCurrencyId = 'USD' AND FX.FiscalPeriodId = FP.FiscalPeriodId
This is what I have in LINQ:
from d in db.Table1
join dv in db.Table2 on d.Table1Id equals dv.Table1Id
join vt in db.Table3 on dv.Table3Id equals vt.Table3Id
join c in db.Table4 on dv.CurrencyId equals c.CurrencyId
join fp in db.Table5 on dv.DVDate >= fp.StartDate && dv.DVDate <= fp.EndDate //error on this line
join fx in db.Table6 on dv.CurrencyId equals fx.FromCurrencyId && fx.ToCurrencyId equals "USD" && fx.FiscalPeriodId equals fp.FiscalPeriodId //error also on this line
The last two joins to fp and fx are the problem but it's not clear to me what's wrong, it doesn't seem to like && but there's no and keyword like there is an equals that replaces =.
I've removed the select portion from LINQ as it's not relevant to the problem and I'd like to avoid spending more time obfuscating table and field names.
"A join clause performs an equijoin. In other words, you can only base matches on the equality of two keys. Other types of comparisons such as "greater than" or "not equals" are not supported. To make clear that all joins are equijoins, the join clause uses the equals keyword instead of the == operator. "
reference: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/join-clause
you need to do this in the where clause. Like here:
https://stackoverflow.com/a/3547706/3058487
To do a join using composite keys, you need to do something like here:
new { dv.CurrencyId, fp.FiscalPeriodId } equals new { CurrencyId = fx.ToCurrencyId, fx.FiscalPeriodId }
Reference:
https://learn.microsoft.com/en-us/dotnet/csharp/linq/join-by-using-composite-keys

How to add || (OR) condition with linq join

How to convert the below SQL query into LINQ query for C#.net
Select t1.id,t2.Name
From table1 t1
INNER JOIN table t2
ON ((t1.column3 is null and t1.id = t2.id)
OR( t.Column3 is NOT NULL and t1.column3 = t3.Column3))
Join tblXYZ xyz on t1.column4 = xys.columnn2
I was unable to add or condition after first set up comparison in linq query, please suggest correct way to achieve this in linq.
Making some assumptions about what you meant, I would suggest hoisting the OR to a union:
(from t1 in table1
join t2 in table2 on t1.Column3 equals t2.Column3
join xyz in tblXYZ on t1.Column4 equals xyz.column2
where t1.Column3 != null).Union(
from t1 in table1
join t2 in table2 on t1.id == t2.id
join xyz in tblXYZ on t1.Column4 equals xyz.column2
where t1.Column3 == null)

Linq to SQL: How to join on no field i.e. cartesian join

How can I make a Linq To SQL join on multiple tables where 1 table should produce a Cartesian product.
To shed some more light, here is a sample of the SQL query.
SELECT Table1.MyField, Setup.SomeField
FROM Table1 INNER JOIN Table2 ON Table1.SomeField = Table2.SomeField, Setup
My Linq to SQL are like:
var q = from t1 in db.Table1
join t2 in db.Table2 on t1.SomeField equals t2.SomeField
join setup in db.Setup
select new {t1.MyField, setup.SomeField};
I'm getting an error on the last join that Type inference failed in the call to 'Join'.
Use SelectMany rather than a Join to perform a Cartesian Product.
In query syntax that would be:
var query = from t1 in db.Table1
from t2 in db.Table2
select new {t1, t2};
This will also do:
var q = from t1 in db.Table1
join t2 in db.Table2 on t1.SomeField equals t2.SomeField
from setup in db.Setup
select new {t1.MyField, setup.SomeField};

Multiple Left Outer Joins in LinqToSql?

Is it possible to accomplish something like this using linqtosql?
select * from table t1
left outer join table2 t2 on t2.foreignKeyID=t1.id
left outer join table3 t3 on t3.foreignKeyID=t1.id
I can make it work using both DataLoad options or join syntax. But the problem is whenever I add a second left join, linqtosql queries with MULTIPLE sql statements, instead of doing a second left join in the underlying sql.
So a query like the one above will result in dozens of sql calls instead of one sql call with 2 left joins.
What are my other options? I can use a view in the DB, but now I'm responsible for creating the hierarchy from the flattened list, which is one of the reasons to use an ORM in the first place.
Note that T2 and T3 are 1:M relationships with T1. Is it possible to have linq efficiently query these and return the hierarchy?
I don't think this is likely to be the right solution to your problem because there are more than one many to one relationships to your parent entity table:
select * from table t1
left outer join table2 t2 on t2.foreignKeyID = t1.id
left outer join table3 t3 on t3.foreignKeyID = t1.id
This is like a person with multiple children and multiple vehicles:
Say t1 is the person
id str
1 Me
Say t2 are the children
PK foreignKeyID str
A 1 Boy
B 1 Girl
Say t3 are the vehicles
PK foreignKeyID str
A 1 Ferrari
B 1 Porsche
Your result set is:
Me Boy Ferrari
Me Girl Ferrari
Me Boy Porsche
Me Girl Porcshe
Which I fail to see how this is a useful query (even in SQL).
Here is a similar question. Be cognizant of how the joins are composed.
For example, the following Linq to Sql query on AdventureWorks:
AdventureWorksDataContext db = new AdventureWorksDataContext();
var productStuff = from p in db.Products
join pl in db.ProductListPriceHistories on p.ProductID equals pl.ProductID into plv
from x in plv.DefaultIfEmpty()
join pi in db.ProductInventories on p.ProductID equals pi.ProductID into pii
from y in pii.DefaultIfEmpty()
where p.ProductID == 764
select new { p.ProductID, x.StartDate, x.EndDate, x.ListPrice, y.LocationID, y.Quantity };
Yielded the same SQL (verified via Profiler) as this SQL query:
SELECT Production.Product.ProductID,
Production.ProductListPriceHistory.StartDate,
Production.ProductListPriceHistory.EndDate,
Production.ProductListPriceHistory.ListPrice,
Production.ProductInventory.LocationID,
Production.ProductInventory.Quantity
FROM Production.Product
LEFT OUTER JOIN Production.ProductListPriceHistory ON Production.Product.ProductID = Production.ProductListPriceHistory.ProductID
LEFT OUTER JOIN Production.ProductInventory ON Production.Product.ProductID = Production.ProductInventory.ProductID
WHERE Production.Product.ProductID = 764
Multiple LEFT JOINs on the Primary Key of the parent table, yielding one generated SQL query.
I would think LINQ to SQL would be able to translate your left outer joins as long as you put the DefaultIfEmpty() calls in the right places:
var q = from t1 in table
join t2 in table2 on t1.id equals t2.foreignKeyID into j2
from t2 in j2.DefaultIfEmpty()
join t3 in table3 on t1.id equals t3.foreignKeyID into j3
from t3 in j3.DefaultIfEmpty()
select new { t1, t2, t3 };
You dont need that terrible join syntax if you FK's are properly setup.
You would probably write:
var q = from t1 in dc.table1s
from t2 in t1.table2s.DefaultIfEmpty()
from t3 in t1.table3s.DefaultIfEmpty()
select new { t1, t2, t3 };

Categories

Resources