LINQ Left Join Error - linq

I have 2 Generic Lists called Students and Entries They are in 1 to 0..1 relationship. Students 1:0..1 Entries
I want to get the list of Student Name, ID and English, Maths detail from Entries if exists.
My LINQ query is as below and I got the null reference error at se.English. Could you pls help? Thanks.
var query = from s in students
join t in entries
on s.StudentDetailID equals t.StudentDetailID into StudentEntries
from se in StudentEntries.DefaultIfEmpty()
select new { s.StudentDetailID, s.LastName, s.FirstName,
se.English};

As you said you have a 1-0..1 relationship so it is possible that se.english will be null(because you used an left outer join), you should handle it in your query, I assumed it it would be String and put "" if it is null, You can handle it as you wish.
var query = from s in students
join t in entries
on s.StudentDetailID equals t.StudentDetailID into StudentEntries
from se in StudentEntries.DefaultIfEmpty()
select new { s.StudentDetailID, s.LastName, s.FirstName,
English = se.English?? ""};

se could be null, because you assigned it StudentEntries.DefaultIfEmpty(). And Default of a class is null. So you can't call se.English without testing it for null before:
var query = from s in students
join t in entries
on s.StudentDetailID equals t.StudentDetailID into StudentEntries
from se in StudentEntries.DefaultIfEmpty()
select new { s.StudentDetailID, s.LastName, s.FirstName,
se == null ? null : se.English};

Related

Linq query not returning expected results even when using DefaultIfEmpty

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();

LINQ Equivalent of and SQL query with two inner joins and a left join

I would be grateful for help with a LINQ equivalent of the following SQL Query (which works). Below this SQL query I give some description of my simple data base and the problem I want to solve.
Select a.Name, a.OrderID,
b.ProductIDFirst, c1.productName ProductNameFirst,
b.ProductIDSecond , c2.productName ProductNameSecond
from Customers a
INNER JOIN ORDERS b ON a.OrderID = b.OrderID
left join products c1 on b.productidfirst = c1.productid
left join products c2 on b.ProductIDSecond = c2.productid
Background information on the database structure:
I have a simple SQL Server Database with three Tables named Products, Orders and Customers.
The business model is such that each order can have only two products (not more).
The Orders table has two foreign keys, though they both come from the Products table. These Foreign Key field Names in the Orders Table are ProductIDFirst and ProductIDSecond. These two Foreign Keys in the orders table correspond to two products that each order can have. Customers table has one Foreign Key which comes from the Orders Table.
Now I need help with an LINQ query that will return me all customers such that I get five fields - CustomerName, OrderID and Names of each of the two products that match the OrderID in the customer product.
Your links are non-existent, so here's a best attempt without seeing anything.
Assuming you have the following containers (you'll need to change them for your scenario):
var customers = new List<Customer>();
var orders = new List<Order>();
var products = new List<Product>();
You can do the following:
var query =
from a in customers
join b in orders
on a.OrderId equals b.OrderId
join c1 in products
on b.ProductIdFirst equals c1.ProductId into c1a
join c2 in products
on b.ProductIdSecond equals c2.ProductId into c2a
from p1 in c1a.DefaultIfEmpty()
from p2 in c2a.DefaultIfEmpty()
select new
{
Name = a.Name,
OrderId = a.OrderId,
ProductIdFirst = p1 == null ? null : p1.ProductIdFirst,
ProductNameFirst = p1 == null ? null : p1.ProductNameFirst,
ProductIdSecond = p2 == null ? null : p1.ProductIdSecond,
ProductNameSecond = p2 == null ? null : p1.ProductNameSecond,
};
In short, where you want a left join, project the join into something else (e.g. c1a, c2a) then call from on them using DefaultIfEmpty() which will set null when no matching item exists on the right-hand-side.

LINQ Select Row with Max date per group

I want to return all the values from my "Categories" table and join that to my "CategorySelections" table to display all the categories and whether the specified user selected them or not.
A complication (which I don't know how to deal with in LINQ) is that the user could have changed his selection/deselection of a particular category over time...each change would have logged in the "CategorySelections" table with a date stamp.
I am after the last selection status.
The following SQL query does what I want:
SELECT cs.UserId, c.CategoryId, m.MaxDate, cs.IsSelected
FROM [myDB].[dbo].[Categories] c
LEFT JOIN [myDB].[dbo].[CategorySelections] cs
ON c.CategoryID = cs.CategoryID AND cs.UserID = 7
INNER JOIN
(
SELECT UserId, CategoryId, Max(CreatedOn) as MaxDate
FROM [myDB].[dbo].[CategorySelections]
GROUP BY UserId, CategoryId
) m
ON cs.UserID = m.UserID AND cs.CategoryID = m.CategoryID AND cs.CreatedOn = m.MaxDate
ORDER BY cs.CategoryI
I need some help getting this done in LINQ.
Below is my attempt, which returns all the selections instead of just the last per category.
var query = from c in db.Category
join cs in db.CategorySelection.Where(x => x.UserID == WebSecurity.CurrentUserId)
on c.CategoryID equals cs.CategoryID into JoinedCategory
from cs in JoinedCategory.DefaultIfEmpty()
select new Selection() { CategoryID = c.CategoryID, CategoryName = c.CategoryName ,IsSelected = cs != null ? cs.IsSelected : false }
I am working in MVC; the "new Selection()" refers to my Model
You may add WHERE statement:
where cs.CreatedOn == CategorySelections.Where(t => t.CategoryId == cs.CategoryId).Max(r => r.CreatedOn)

LINQ Query returning null results into anonymous variable

What is wrong with my query code? It is returning null, it's no different to other examples I've looked at. What may be some of the reasons the "buildings" var is null.
The tables are not empty and should be returning some results.
Tables in database:
Buildings(BuildingId, BuildingName)
UsersBuildings(UserId, BuildingId, BuildingQuantity, BuildingLevel)
var buildings =
from b in imDB.Buildings
join ub in imDB.UsersBuildings
on b.BuildingId equals ub.BuildingId
where ub.UserId == userId
select new { b.BuildingName, ub.BuildingLevel};
BuildingLevel is in Buildings table so your query should be like
var buildings =
from b in imDB.Buildings
join ub in imDB.UsersBuildings
on b.BuildingId equals ub.BuildingId
where ub.UserId == userId
select new { b.BuildingName, b.BuildingLevel};

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.

Resources