LINQ to SQL - Left OUTER Join with two joins not working - c#

In the following LINQ Query I need to display All the customers including the customers that have not placed any order but whose order is priced more than $100. But my following LINQ Query is returning all the customers regardless of their order price. It seems to be ignoring my Where(ord => ord.price > 100) clause in the LINQ query below. What I may be doing wrong?
Models:
public class Customer
{
public int CustomerId { get; set; }
public string CustName{ get; set; }
}
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public float price { get; set; }
}
LINQ Query:
var Query1 = from c in Customers
join ord in Orders on c.CustomerId equals ord.CustomerId into cord into cord
from t in cord.Where(ord => ord.price > 100).DefaultIfEmpty()
select new {CustName= c.Name, OrderID = (t == null ? 0 : t.OrderId)};
SQL Query:
I want to translate following T-SQL query into LINQ query:
SELECT c.Name, OrderID
FROM Customers c
LEFT OUTER JOIN Orders ord
ON c.CustomerID = ord.CustomerID
AND ord.Price > 100

Well, I don't have your data to be able to verify this, but one issue that jumps out at me is you're not specifying the field (CustomerId) on which to join the two collections.
Try modifying your query like this: (adding on c.CustomerId equals ord.CustomerId)
var Query1 = from c in Customers
join ord in Orders on c.CustomerId equals ord.CustomerId into cord
from t in cord.DefaultIfEmpty()
where t.price > 100
select new {CustName = c.Name, OrderID = (t == null ? 0 : t.OrderId)};

Related

How to optimize this query using EF

Hi good day I am new with Entity Framework. I just wanna to know if there is a way I could improve my implementation. Here are the codes.
public async Task<List<Record>> GetRecordsByBatchId(string batchId, string source)
{
List<string> idList = new List<string>();
//[1] Get all parent ID from table 1 with a filter of source and batchId
var parentIds= await _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => b.ParentId).ToListAsync();
if (parentIds.Count() == 0)
{
return new List<Record>();
}
//[2] Query idNumber of each parentId from [1] to SecondTable
List<long> idNumber = await _context.Set<SecondTable>()
.Where(a => parentIds.Contains(a.Id))
.Select(b => b.IdNumber).ToListAsync();
//[3] Query Record/s that contains idNumber from previous query [2]. it is possible that 1 or
//more records has same idNumber
List<Risk> recordByIdNumber = await _context.Set<SecondTable>()
.Where(a => idNumber.Contains(a.IdNumber)).ToListAsync();
//[4] In this part I just want to group the records in [3] by Id number and sort each group
//by its endorsementNumber in descending order and return the record with highest endorsement
//number for each group
return (from record in recordByIdNumber
group record by record.IdNumber into g
orderby g.Key
select g.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault()).ToList();
}
}
The model for the FirstTable
public class FirstTable
{
public Guid? ParentId{ get; set; }
public string BatchId { get; set; }
public string Source { get; set; }
public bool IsActive { get; set; }
}
The model for the SecondTable
public class SecondTable
{
public Guid Id{ get; set; }
public int EndorsementNumber { get; set; }
public long IdNumber { get; set; }
}
Note: I just include the necessary properties in the model.
This approach is working as expected. I just wanna know if there is a possibility that these queries could be optimized that there is only 1 query for the SecondTable table.
Any help would be greatly appreciated, thanks in advance.
Yes, queries 1-3 can and should be combined. In order to do that you need, to have navigation properties in your model. It seems that there is one-to-many relationship between FirstTable and SecondTable. Let's use Customer and Order instead.
class Customer {
int CustomerId
string BatchId
ICollection<Order> Orders
}
class Order {
int OrderId
int CustomerId
Customer Customer
Risk Risk
}
in which case you just write third query as
List<Risk> = await _context.Orders.Where(o => o.Customer.BatchId == batchId)
.Select(o => o.Risk).ToListAsync();
Obviously, I am only guessing the structure and the relationship. But hopefully, this can get you started. For me Contains() is "code smell". There is a high chance that there will be large list out of your first query, and contains() will produce a huge IN clause in the database, that can easily crash the system
var parentIds = _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => new { b.parentId });
var risks = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArrayAsync();
return risks;
You can have 1 query instead of 3. It will perform better as the number of the rows from the first query grows.
EDIT: As #SvyatoslavDanyliv mentioned in the comments, group-take operations may not work depending on the version of the EF and the provider you use. You may need to separate the query and the group by operation like below :
var result = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).ToArrayAsync();
var risks = result.GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(
risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArray();
return risks;

LINQ Query - Display customers with their number of orders including the customer with no orders

In the following LINQ Query I need to display All the customers with total number of their orders placed including the customers that have not placed any orders:
Models:
Public class Customer
{
public int CustomerId { get; set; }
public string Name{ get; set; }
}
Public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
}
LINQ Query: Question: How can I display ALL customers (including the one that have no order) and total number of orders per customer (zero for those whose customerID is not in Orders table)
var Query1 = from c in Customers
join o in Orders into co
from t in co.DefaultIfEmpty()
select new {CustomerID = c.CustomerId, OrderID = (t == null ? 0 : t.OrderId)};
You basically need to do a LEFT JOIN between your Customer table and Customer Order table and then do a group by on that result to count the orders for each customer.
Assuming you have a class like this
public class CustomerOrder
{
public int CustomerId { set; get; }
public int? OrderId { set; get; }
}
This class is to store each item for the left join result
Now, you need to first do a LEFT JOIN and project the result of that to a list of CustomerOrder class objects, Then do a GroupBy on top of that
var usersWithCount = (from c in db.Customers
join o in db.Orders on c.CustomerId equals o.CustomerId
into result
from sub in result.DefaultIfEmpty()
select new CustomerOrder{ OrderId= sub!=null ? sub.OrderId :(int?) null,
CustomerId = u.CustomerId }
) // Here we have the left join result.
.GroupBy(g => g.CustomerId , (k, orders) => new {
CustomerId = k,
OrderCount = orders.Count(t=>t.OrderId !=null)}).ToList();
The result stored in usersWithCount will be a collection of annonymous object with a CustomerId and OrderCount property.
This isn't very efficient since it iterates through Orders for every customer, but it'll get the job done:
var query = Customers
.Select(c => new
{
Name = c.Name,
NumOrders = Orders.Count(o => o.CustomerId = c.CustomerId)
});
foreach (var result in query)
Console.WriteLine("{0} -> {1}", result.Name, result.NumOrders);
var Query1 = from c in Customers
join o in Orders on c.CustomerId equals o.CustomerId into OrdersGroup
from item in OrdersGroup.DefaultIfEmpty(new Order { OrderId = 0, CustomerId = 0 })
select new {CustomerID = c.CustomerId, OrderID = (item == null ? 0 : item.OrderId)};
this will return all customers (even if they have no orders ) and orders
https://dotnetfiddle.net/BoHx2d

How can I use Left join in linq that we use in sql?

How can I use Left join in Linq that I write SQL query?
select
p.Name, p.Family,
E.EmployTypecode, E.employtypeName, E.EmplytyppeTye
from
personnel as p
left join
Employee as E on E.EmployTypecode = p.EmployTypecode
Use Join keyword instead of Left join and it is mandatory to use "INTO" keyword and "DefaultIfEmpty()" method as right table returns null value.
var query = from p in personnel
join e in Employee on p.EmployTypecode equals e.EmployTypecode into t
from nt in t.DefaultIfEmpty()
orderby p.Name
select new
{
p.Name, p.Family,
EmployTypecode=(int?)nt.EmployTypecode, // To handle null value if Employtypecode is specified as not null in Employee table.
nt.employtypeName, nt.EmplytyppeTye
}.ToList();
Do it like this :
var query =
from p in personnel
join e in Employee
on p.EmployTypecode equals e.EmployTypecode
into temp
from j in temp.DefaultIfEmpty()
select new
{
name = p.name,
family = p.family,
EmployTypecode = String.IsNullOrEmpty(j.EmployTypecode) ? "" : j.EmployTypecode,
......
}
var q=(
from pd in dataContext.personnel
join od in dataContext.Employee
on pd.EmployTypecode equals od.EmployTypecode
into t
from rt in t.DefaultIfEmpty()
orderby pd.EmployTypecode
select new
{
EmployTypecode=(int?)rt.EmployTypecode,
pd.Name,
pd.Family,
rt.EmplytyppeTye
}
).ToList();
Why dont use SQL query to convert EF to LIST.
In EF 6.1
write
public class personnel
{
public String Name { get; set; }
public String Family { get; set; }
public String EmployTypecode { get; set; }
public String employtypeName { get; set; }
public String EmplytyppeTye { get; set; }
}
List<personnel> personnels = dbentities.Database.SqlQuery<personnel>(#"select
p.Name, p.Family,
E.EmployTypecode, E.employtypeName, E.EmplytyppeTye
from
personnel as p
left join
Employee as E on E.EmployTypecode = p.EmployTypecode ").ToList();

Linq - Join where ID's !=, select new + distinct?

I have the following classes.
Course;
public class Course
{
//pk
public int Id{ get; set; }
public int SourceCourseId { get; set; }
public string Name { get; set; }
}
Registration
public class Registration
{
//primary key
public int Id { get; set; }
//...more fields
public int CourseId { get; set; }
}
I want to obtain a collection of annonymous objects with the two fields below for all Courses that are Distinct in the registrations table that are not in the Courses table.
var distinctCourses = (from registration in db.Registrations
join courses in db.Courses on registration.CourseId equals courses.SourceCourseId
where registration.CourseId != courses.SourceCourseId
select new
{
SourceCourseId = registration.CourseId,
Name = registration.CourseName,
}).Distinct().ToList();
For some reason the above is returning 0... Any suggestions?
try a left join:
var query = from r in registrations
join c in courses on r.CourseId equals c.id into newCourses
from nullCourse in newCourses.DefaultIfEmpty()
where nullCourse == null
select new { }
Edit - per comment from Alex :
Also, your where clause needs to change to
where nullCourse == null
Edit - changed join columns and added correct where clause.
Edit - group registrations on CourseID so they will be distinct
var distinctCourses =
(from registration in db.Registrations
group registration by registration.CourseId into grp
from reg in grp
join courses in db.Courses on reg.CourseId equals courses.SourceCourseId into newCourses
from nullCourse in newCourses.DefaultIfEmpty()
where nullCourse == null
select new
{
SourceCourseId = reg.CourseId,
Name = reg.CourseName,
}).ToList();
Try this
var result = Registrations.GroupJoin(Courses,r=>r.CourseId,c=>c.SourceCourseId,
(k,g) => new {k,g})
.Where(x=>x.g.Count()==0)
.Select(s=> new {id=s.k.CourseId,name=s.k.CourseName});

sql to return list of parents and their nested child collections

I've googled this for hours but couldn't get a satisfactory answer. Lets say I have a table tblCustomers and a related tblOrders. This is your typical parent - child relationship where you can have only one unique customer in tblCustomers but the customer can have multiple orders in tblOrders. Now in C# I have the Customer and Orders objects like this
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//Nested child Order collection
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public string ItemName { get; set; }
public decimal Amount { get; set; }
}
How do you return a List of Customers and their nested Orders in one transaction? I have tried this sql
var sql = #"SELECT c.*,
(SELECT TOP 100 o.* FROM [dbo].[tblOrders] AS o
WHERE c.Id = o.CustomerId
ORDER BY o.ItemName ASC) AS Orders
FROM [dbo].[tblCustomers] AS c";
var listOfCustomers = dbContext.Database.SqlQuery<Customer>(sql).ToList();
but I get this error
Only one expression can be specified in the select list when the
subquery is not introduced with EXISTS.
Anyone spot an error in my sql (sql is not my cup of tea) or know of a better sql that will accomplish the same in one transaction?
we can't use * in subquery we have to specify 1 column The nested query must return only one column for comparison
SELECT c.*,
(SELECT TOP 100 o.CustomerId FROM [dbo].[tblOrders] AS o
WHERE c.Id = o.CustomerId
ORDER BY o.ItemName ASC) AS Orders
FROM [dbo].[tblCustomers] AS c
we can make it like that also
SELECT c.*
FROM [dbo].[tblCustomers] AS c
WHERE c.Id in
(SELECT TOP 100 o.CustomerId FROM [dbo].[tblOrders] AS o
c.Id = o.CustomerId
ORDER BY o.ItemName ASC)
using Linq
var CustOrd = (from c in dbContext.Customers
join o in dbContext.Orders on c.Id equals o.CustomerId
select new
{
c,
o
}).ToList();

Categories