Linq query not returning expected results even when using DefaultIfEmpty - linq

I have the following query in one of my Entity Framework Core API controllers:
var plotData = await (from nl in _context.BookList
join ql in _context.PlotList on nl.PlotId equals ql.PlotId
join qc in _context.PlotChoices on ql.PlotId equals qc.PlotId
join nk in _context.BookLinks.DefaultIfEmpty() on qc.ChoiceId equals nk.ChoiceId
where nl.Id == ID
select new
{ .. }
I need it to return all rows even if data doesn't exist in the BookLinks table.
However, it's not returning rows if there is no data data in the BookLinks table for that row.
But this SQL query, from which I'm trying to model from, does return data...it returns nulls if there is no data in BookLinks.
select * from BookList bl
left join PlotList pl ON bl.plotId = bl.plotId
left join PlotChoices pc ON pl.plotId = pc.plotId
left join BookLinks bk ON pc.choiceID = bk.choiceID
where nl.caseID = '2abv1'
From what I read online, adding 'DefaultIfEmpty()' to the end of BookLinks should fix that, but it hasn't.
What am I doing wrong?
Thanks!

When using left join , you can try below code sample :
var plotData = (from nl in _context.BookList
join ql in _context.PlotList on nl.PlotId equals ql.PlotId
join qc in _context.PlotChoices on ql.PlotId equals qc.PlotId
join nk in _context.BookLinks on qc.ChoiceId equals nk.ChoiceId into Details
from m in Details.DefaultIfEmpty()
where nl.Id == ID
select new
{
}).ToList();

Related

EF linq query working in linqpad but not in the App

I have creatred a query in linqpad and it works but when i try to use it in my blazor app the app throws an error.
I have three tables, tblOpportunity, tblOppStatus and lkpStatus
and an opportunity can have many status's depending on how mature the enquiry is. So i need to get all the latestest status for each opportunity. I also want to be able to filter the dataset so i get only opportunities with staus of x, y and z
void Main()
{
var output=from o in TblOpportunities
join sub in (
//get the latest status ID for the from the joining table
from smax in TblOppStatuses
group smax by smax.OpportunityID
into g
select new
{
OName = g.Key,
MaxS = (from t2 in g select t2.OppStatusID).Max()
})
on o.OpportunityID equals sub.OName
join st in TblOppStatuses on sub.MaxS equals st.OppStatusID
//get the statu sname form the lookup table
join lst in Lkp_Statuses on st.StatusID equals lst.StatusID
join c in TblClients on o.ClientID equals c.ClientID
select new
{
c.ClientName,
o.OpportunityName,
sub.MaxS,
lst.Status,
lst.StatusID
};
int[] filter = { -1, 62};
output.Where(of=>filter.Contains(of.StatusID))
.Dump();
}
The error that comes in the subquery
MaxS = (from t2 in g select t2.OppStatusID).Max()
if i comment this out the query runs but doesn't give me what i want. Can anyone advise what i am doing wrong As I say it works fine in Linqpad.

Linq Nested Inner Joins

I want to join the following Tables
1. B_Book[1st Table]
-B_BID (Book ID)(PK)
-B_Name
-B_CategroyID (FK)
2. BI_BookInstance [2nd Table]
-BI_IID(Instance ID)
-BI_BID (FK)
-BI_Price
3. BC_BookCategory [3rd Table]
-BC_CategoryID (PK)
-BC_CategoryName
First Join B_Book and BI_BookInstance then join the result of those both with BookCategory.
(1st join)[B_BID equals BI_BID]
(2nd nested join)[result of 1st join B_CategoryID equals BC_CategoryID]
Edit
SQL would be something like the following:
SELECT * FROM
(SELECT * FROM B_Book b JOIN BI_BookInstance bi on b.B_BID = bi.BI_BID) as t1
JOIN BC_BookCategoryID bc on bc.BC_CategoryID = t1.B_CategoryID
What matches your query in LINQ would be the following (and you'll notice the similarity with SQL). I've also included some examples on how to rename the fields returned, such as Price or CategoryName:
var results = from b in B_Book
join bi in BI_BookInstance
on b.B_BID equals bi.BI_BID
join bc in BC_BookCategory
on b.B_CategoryID equals bc.BC_CategoryID
select new
{
// put in whatever fields you want returned here:
b.B_BID,
b.B_CategoryID,
b.B_Name,
bi.BI_BID,
bi.BI_IID,
Price = bi.BI_Price,
bc.BC_CategoryID,
CategoryName = bc.BC_CategoryName
};
I have supposed inner joins (your FKs is not null), so i would like query like this:
var ctx = new YourEntities();
var query = from b in ctx.B_Book
from bi in ctx.BI_BookInstance
from bc in ctx.BC_BookCategory
where b.B_BID == bi.BI_BID && b.B_CategoryID == bc.BC_CategoryID
select new
{
BInstID = bi.BI_IID,
BName = b.B_Name,
BPrice = bi.BI_Price,
BCategory = bc.BC_CategoryName
};
foreach (var item in query)
{
Console.WriteLine(item.BInstID);
Console.WriteLine(item.BName);
Console.WriteLine(item.BPrice);
Console.WriteLine(item.BCategory);
Console.WriteLine("");
}
You can do this without explicitly using linq's join statement, provided that navigation properties are in place:
from b in ctx.B_Book
from bi in b.BookInstances
select new { b.Property1, bi.Property2, b.BookCategory.Name }

Left Outer Join works in LINQPad (connected using EF 4.3) but fails in application

I have an application that successfully utilizes LINQ to perform LEFT OUTER JOINs several instances; however, in one case it fails to work as expected.
Testing in LINQPad (using LINQ-to_SQL) produced the correct result; however, to be sure I changed to the LINQPad beta version 4.42.05 and successfully connected using my application's DLL and the connectionString from its web.config file (as per the Add Connection dialog). Again, LINQPad is successful in returning the proper result and clearly generates the expected left outer join in TSQL but the same code in the application fails.
In debugging the function, I get the "Object reference not set to an instance of an object." error. Please see additional descriptions after the following code and related TSQL. Note the relationship involves customer who have one-or-more stores and whoes stores have zero-or-more departments. Therefore, some returned records will not have a department (hence the requirement for the left outer join).
The following code works perfectly in LINQPad:
var model = (from h in SalesOrderHeaders
join c in Customers on h.CustomerId equals c.CustomerId
join s in Stores on h.StoreId equals s.StoreId
join d in Departments on h.DepartmentId equals d.DepartmentId into outer
from o in outer.DefaultIfEmpty()
select new
{
OrderId = h.SalesOrderHeaderId,
OrderDetailId = 1,
SalesOrderDate = h.SalesOrderDate,
DeliveryDateTime = h.DeliveryDateTime,
Customer = c.Customer,
Store = s.Store,
Department = (o.Department == null) ? "None" : o.Department,
FullDescription = "None",
Qty = 0,
UoM = "None",
}).OrderBy (m => m.OrderId);
When the code below is used in the application it fails:
var model = from h in headers
join c in customers on h.CustomerId equals c.CustomerId
join s in stores on h.StoreId equals s.StoreId
join d in departments on h.DepartmentId equals d.DepartmentId into outer
from o in outer.DefaultIfEmpty()
select new SalesOrderGridViewModel
{
OrderId = h.SalesOrderHeaderId,
OrderDetailId = 1,
SalesOrderDate = h.SalesOrderDate,
DeliveryDateTime = h.DeliveryDateTime,
Customer = c.Name,
Store = s.Name,
Department = (o.Name == null) ? "None" : o.Name,
FullDescription = "None",
Qty = 0,
UoM = "None",
};
However, when I change the code in the application so that the boolean in the assignment of the department field of the result references the join element from the headers variable (h.DepartmentId == null) as in the following code:
var model = from h in headers
join c in customers on h.CustomerId equals c.CustomerId
join s in stores on h.StoreId equals s.StoreId
join d in departments on h.DepartmentId equals d.DepartmentId into outer
from o in outer.DefaultIfEmpty()
select new SalesOrderGridViewModel
{
OrderId = h.SalesOrderHeaderId,
OrderDetailId = 1,
SalesOrderDate = h.SalesOrderDate,
DeliveryDateTime = h.DeliveryDateTime,
Customer = c.Name,
Store = s.Name,
Department = (h.DepartmentId == null) ? "None" : o.Name,
FullDescription = "None",
Qty = 0,
UoM = "None",
};
The expected result is returned.
Interestingly the subtle difference in the TSQL generated first from the original code:
SELECT [t4].[SalesOrderHeaderId] AS [OrderId], [t4].[SalesOrderDate],
[t4].[DeliveryDateTime], [t4].[Customer], [t4].[Store],
[t4].[value] AS [Department]
FROM (
SELECT [t0].[SalesOrderHeaderId], [t0].[SalesOrderDate],
[t0].[DeliveryDateTime], [t1].[Customer], [t2].[Store],
(CASE
WHEN [t3].[Department] IS NOT NULL THEN [t3].[Department]
ELSE CONVERT(NVarChar(50),#p0)
END) AS [value]
FROM [SalesOrderHeaders] AS [t0]
INNER JOIN [Customers] AS [t1] ON [t0].[CustomerId] = [t1].[CustomerId]
INNER JOIN [Stores] AS [t2] ON [t0].[StoreId] = ([t2].[StoreId])
LEFT OUTER JOIN [Departments] AS [t3]
ON [t0].[DepartmentId] = ([t3].[DepartmentId])) AS [t4]
ORDER BY [t4].[SalesOrderHeaderId]
And here from the revised code, where the boolean was changed to test the value of the DepartmentId in the original headers table ([t3].[Department] versus [t0].[DepartmentId] ), appears to be the solution:
SELECT [t4].[SalesOrderHeaderId] AS [OrderId], [t4].[SalesOrderDate],
[t4].[DeliveryDateTime], [t4].[Customer], [t4].[Store],
[t4].[value] AS [Department]
FROM (
SELECT [t0].[SalesOrderHeaderId], [t0].[SalesOrderDate],
[t0].[DeliveryDateTime], [t1].[Customer], [t2].[Store],
(CASE
WHEN [t0].[DepartmentId] IS NOT NULL THEN [t3].[Department]
ELSE CONVERT(NVarChar(50),#p0)
END) AS [value]
FROM [SalesOrderHeaders] AS [t0]
INNER JOIN [Customers] AS [t1] ON [t0].[CustomerId] = [t1].[CustomerId]
INNER JOIN [Stores] AS [t2] ON [t0].[StoreId] = ([t2].[StoreId])
LEFT OUTER JOIN [Departments] AS [t3]
ON [t0].[DepartmentId] = ([t3].[DepartmentId])) AS [t4]
ORDER BY [t4].[SalesOrderHeaderId]
While I have found a way to make this work; because it works both ways in LINQPad and successfully in numerous other LINQ queries scattered throughout my application, its failure in the original form in this one location gives me concern.
Ultimately, it appears to fail in the application when I test the returned value of the left outer join. However, this is the documented practice in many books and articles. So my final question is does anyone have any insight into why this would occur and/or how it can work in LINQPad (using the applications DLL and against the same DB)?
This is a classic example of how not to write LINQ queries - thinking in SQL and then transliterating into LINQ.
With LINQ, you can avoid the joins completely and formulate your query as follows:
from h in SalesOrderHeaders
orderby h.OrderId
select new
{
OrderId = h.SalesOrderHeaderId,
OrderDetailId = 1,
SalesOrderDate = h.SalesOrderDate,
DeliveryDateTime = h.DeliveryDateTime,
c.Customer.Customer,
s.Store.Store,
Department = (h.Department == null) ? "None" : h.Department.Department,
FullDescription = "None",
Qty = 0,
UoM = "None"
}
It appears that [t0].DepartmentID (from SalesOrderHeaders) can be null. Your LEFT OUTER JOIN relies on this value. This ends up comparing [t3].DepartmentID to null (in some cases).
You might need to use your CASE statement from the select in the JOIN as well.

Join statement in Linq to Sql

I need to write Join statment after writing query in linq
example :
var Query = (from Tab in Db.Employees
select Tab)
as i have some cases to perform join operation so
i need to do it on this Query Query.Join(Join with another Table like Department); I need the Syntax
if (DeptID != -1){ Query.Join(Join with table Department where FkDeptID = DeptID); }
Consider the usage of join in the LINQ 'query syntax':
from t1 in db.Table1
join t2 in db.Table2 on t1.field equals t2.field
select new { t1.field2, t2.field3}
Something like this?
var results = (from q in Query
join m in myList on q.SomeID = m.SomeID
select unknown);
Try using this query:
var Query =
from e in Db.Employees
join d in Db.Departments on e.FkDeptID equals d.DeptID into departments
select new
{
Employee = e,
Department = departments.SingleOrDefault(),
};
This works assuming that when e.FkDeptID == -1 that there is no record in the Departments table and in that case Department would be assigned null.
You should never have more than one department for an employee so I've used SingleOrDefault rather than FirstOrDefault.

many to many relationship

I am trying to write a linq to get data from many to many tables.
Here are the tables
Products (ID,Name,Description)
Products_Items (ID,ProductID,Description)
ProductsNeeds (ID,Name)
ProductsItems_Needs (ItemID,NeedsID)
This is the t-sql query
select gPro.Name,gProItems.ShortDescription,gProItems.Description,gNeeds.Name
from Products gPro
join Products_Items gProItems on gPro.ID = gProItems.ProductID
join ProductsItems_Needs gProNeeds on gProNeeds.ItemID = gProItems.ID
join ProductsNeeds gNeeds on gNeeds.ID = gProNeeds.NeedsID
where gProItems.ID = 1
this is the linq
var q = from p in objM.Products
join gpItems in objM.Products_Items on p.ID equals gpItems.ProductID
from needs in gpItems.ProductsNeeds
where gpItems.ID == 1
select p;
This query returns (Products) and it has the Produts_Items but it has not the ProductsNeeds.
What modifications should I do in order each Products_items to have the ProductsNeeds?
Thanks
Finally I found the solution.
The change was that instead of returning Products it returns Product_Items.
var q = from pItems in objM.Products_Items
join p in objM.Products on pItems.ID equals p.ID into joinedProducts
from p in joinedProducts.DefaultIfEmpty()
from needs in pItems.ProductsNeeds
where pItems.ID == 1
select pItems;

Resources