Linq left join with group join - c#

I have set of Users and Visits. (So user do visits)
Visit have User navigation property.
I need to find the users who don't visit.
I can do this by finding the users who visit, finding all of the users then taking the difference.
I was trying to find a solution which is faster.
This is what I have right now:
var users = _db.Users.AsNoTracking().Include(c => c.City).Where(x => x.City.Id == city);
var groupedUsers = _db.Visits.AsNoTracking().Include(c => c.City).Include(a=>a.VisitedBy).Where(x => x.City.Id == city).GroupBy(x => x.VisitedBy).Select(group => new { VisitedBy = group.Key, Count = group.Count() });
var visitingUsers = groupedUsers.Select(user => user.VisitedBy);
var dif = users.Except(visitingUsers);
However I was trying GroupJoin as below:
var results = _db.Users.Include(c => c.City).Where(c => c.City.Id == city).
GroupJoin(_db.Visits.Include(c => c.City).Include(u => u.VisitedBy), u => u.Id, v => v.VisitedBy.Id, (u, v) => new { User = u , Visits = v })
.Select(o=>o.User);
But this gives me all of the Users, I want the users who don't exist in the Visit set.
Any help?

You may be able to avoid the correlated sub-query in the other answer by actually doing the left join with null check. Here's a quick example:
var A = new [] { new Foo { Bar = 1 }, new Foo { Bar = 2 }};
var B = new [] { new Foo { Bar = 2 }};
var C = from x in A
join y in B on x.Bar equals y.Bar into z
from y in z.DefaultIfEmpty()
where y == null
select x;
Check the emitted SQL...

I am not too sure if the city filtering is what you are after however the following should achieve what you desire:
var visitsToCity = _db.Visits.Where(v => v.City.Id == city);
var usersOfCity = _db.Users.Where(u => u.City.Id == city);
var nonVisitingUsers = usersOfCity.Where(u => !visitsToCity.Any(v => v.VisitedBy == u));
The last Where combined with the Any should result in a SQL statement like:
SELECT * FROM Users u WHERE u.CityId = #p0 AND
NOT EXISTS(SELECT 1 FROM Visits v WHERE v.CityId = #p0 AND
v.VisitedById = u.Id)
Where #p0 will be supplied with the value of city.

Related

DefaultIfEmpty() does not handle empty collections

I've been trying to left join the table and they are in a one-to-many relationship.
I have written a SQL query and trying to convert it into LINQ for my ASP.NET Core application.
My sql query is as follows:
SELECT ap.SystemId,
ap.AccessRequiredToId,
cb.AccessAreaManagementId,
ap.EquipmentTagId,
COUNT(ap.Name) [Count]
FROM ApplicationForms ap LEFT JOIN AccessAreaCheckBoxes cb
ON n ap.RecordId = cb.RecordId
WHERE EndDate IS NULL AND (Checked IS NULL OR Checked = 1)
GROUP BY ap.SystemId, ap.AccessRequiredToId, cb.AccessAreaManagementId, ap.EquipmentTagId
SQL Result
And my LINQ is as follows:
var active = _context.ApplicationForms
.Where(w => w.EndDate == null)
.GroupJoin(_context.AccessAreaCheckBoxes
.Where(w => (w.AccessAreaManagement == null || w.Checked == true)),
x => x.RecordId,
y => y.RecordId,
(x, y) => new { ApplicationForms = x, AccessAreaCheckBoxes = y })
.SelectMany(x => x.AccessAreaCheckBoxes.DefaultIfEmpty(),
(x, y) => new { x.ApplicationForms, AccessAreaCheckBoxes = y })
.GroupBy(g => new { g.ApplicationForms.System, g.ApplicationForms.AccessRequiredTo, g.AccessAreaCheckBoxes.AccessAreaManagement, g.ApplicationForms.EquipmentTag })
.Select(s => new RecordViewModel
{
System = s.Key.System.Name,
AccessRequiredTo = s.Key.AccessRequiredTo.Name,
AccessArea = s.Key.AccessAreaManagement.Name,
EquipmentTag = s.Key.EquipmentTag.Name,
Count = s.Count()
}).ToList();
Everything is working well except it doesn't show the rows with the NULL value.
Did I miss out something in my LINQ?
Any help would be greatly appreciated!
This is what I do in the end, post here for your reference.
var active = (from ap in _context.ApplicationForms
join cb in _context.AccessAreaCheckBoxes
on ap.RecordId equals cb.RecordId into j1
from j2 in j1.DefaultIfEmpty()
where ap.EndDate == null
&& (j2.AccessAreaManagement == null || j2.Checked == true)
group new { ap.System, ap.AccessRequiredTo, j2.AccessAreaManagement, ap.EquipmentTag }
by new { System = ap.System.Name, Building = ap.AccessRequiredTo.Name, AccessArea = j2.AccessAreaManagement.Name, Equipment = ap.EquipmentTag.Name } into grp
select new RecordViewModel
{
System = grp.Key.System,
AccessRequiredTo = grp.Key.Building,
AccessArea = grp.Key.AccessArea,
EquipmentTag = grp.Key.Equipment,
Count = grp.Count()
}).ToList();

C# Linq Lambda Left Outer Join

I need to create a LEFT OUTER JOIN in linq lambda syntax. The SQL I am trying to create a linq equivalent of is:
SELECT DISTINCT
p.PartNum AS PartNum, p.ShortChar01 AS SkuType,
vv.VendorID AS VendorCode,
p.PartDescription AS Description, p.Company AS Company
FROM
Part p WITH (NOLOCK)
INNER JOIN
PartPlant pp ON p.Company = pp.Company AND p.PartNum = pp.PartNum
LEFT OUTER JOIN
Vendor vv On pp.VendorNum = vv.VendorNum
WHERE
p.RefCategory = #refCategory
So as you can see its a fairly simple query joining a few tables. The issue is that it could happen that there is no vendor but we still want the rest of the information hence the left outer join.
My current attempt to recreate this is:
_uow.PartService
.Get()
.Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v });
I am aware that the select isn't returning the same fields at the moment. I have ignored that for now as I am trying to ensure i am getting the correct values first.
The SQL query returns 41 records with 1 record having a null vendor. The linq query returns 40 records obviously not returning the one with the null vendor. I have tried using GroupJoin() and DefaultIfEmpty() but I cannot get it to work.
Any help would be greatly appreciated.
From the comment and links from user2321864, I managed to get it working as follows:
_uow.PartService.Get().Where(p => p.RefCategory.Equals(level2))
.Join(_uow.PartPlantService.Get(),
p => new { p.PartNum, p.Company },
pp => new { pp.PartNum, pp.Company },
(p, pp) => new { Part = p, PartPlant = pp })
.GroupJoin(_uow.VendorService.Get(),
pprc => pprc.PartPlant.VendorNum,
v => v.VendorNum,
(pprc, v) => new { PPRC = pprc, V = v })
.SelectMany(y => y.V.DefaultIfEmpty(),
(x, y) => new { PPRC = x.PPRC, Vendor = y })
.Select(r => new Level2Parts()
{
CompanyCode = r.PPRC.Part.Company,
Description = r.PPRC.Part.PartDescription,
PartNum = r.PPRC.Part.PartNum,
SkuType = r.PPRC.Part.ShortChar01,
VendorCode = r.Vendor.VendorID
})
.Distinct();

Grouping and Sum some field with Sub query in LINQ

I'm trying to convert my sql query to linq, i confused about sum and grouping,
this is my query
SELECT
produk.supplier,
SUM(transaksi.jumlah_transaksi),
SUM(transaksi.nominal_transaksi),
operasional.nominal
FROM
transaksi INNER JOIN produk ON transaksi.id_produk = produk.id_produk
LEFT JOIN
(SELECT
operasional.id_supplier,
SUM(nominal) AS nominal
FROM
operasional) operasional
ON operasional.id_supplier = produk.id_supplier
GROUP BY produk.supplier
output should be
like this
Progress
i am just trying with linq query like this without grouping
var result = from t in db.transaksi
join p in db.produk on t.id_produk equals p.id_produk
from op in
(
from o in db.operasional
select new
{
id_supplier = o.id_supplier,
nominal = o.nominal
}
).Where(o => o.id_supplier == p.id_supplier).DefaultIfEmpty()
select new
{
nama_supplier = p.supplier,
jumlah_transaksi = t.jumlah_transaksi,
nominal_transaksi = t.nominal_transaksi,
biaya_operasional = op.nominal
};
and result query from my linq still like this
SELECT
`p`.`supplier`,
`t`.`jumlah_transaksi`,
`t`.`nominal_transaksi`,
`t1`.`nominal`
FROM
`transaksi` `t`
INNER JOIN `produk` `p`
ON `t`.`id_produk` = `p`.`id_produk`
LEFT JOIN `operasional` `t1`
ON `t1`.`id_supplier` = `p`.`id_supplier`
Solved
and this is my full linq
var result = from t in db.transaksi
join p in db.produk on t.id_produk equals p.id_produk
from op in
(
from o in db.operasional
group o by o.id_supplier into g
select new
{
id_supplier = g.First().id_supplier,
nominal = g.Sum(o => o.nominal)
}
).Where(o => o.id_supplier == p.id_supplier).DefaultIfEmpty()
select new
{
nama_supplier = p.supplier,
jumlah_transaksi = t.jumlah_transaksi,
nominal_transaksi = t.nominal_transaksi,
biaya_operasional = op.nominal
};
var grouped = result
.GroupBy(x => x.nama_supplier)
.Select(x => new
{
nama_supplier = x.Key,
jumlah_transaksi = x.Sum(s => s.jumlah_transaksi),
nominal_transaksi = x.Sum(s => s.nominal_transaksi),
biaya_operasional = x.Select(s => s.biaya_operasional).First()
});
Try to use GroupBy (in following code result is your query from code above):
var grouped = result
.GroupBy(x => x.nama_supplier)
.Select(x => new {
nama_supplier = x.Key,
sum1 = x.Sum(s => s.jumlah_transaksi),
sum1 = x.Sum(s => s.nominal_transaksi),
nominal = x.Select(s => s.biaya_operasional).First()
})
Code is not checked so use it just as idea.

convert sql to linq with two tables

select ind.desc,ind.number
from int_goals_df idd, goals_df ind
where idd.dld_number = 123456
and ind.number = idd.ind_number
and ind.categorie = 2
order by follownumber
I'm having a hard time translating this to linq since it is using two tables.
I've currently solved this now imperatively with a foreach loop but not happy with it..
I'm trying to get a list of goals_df that matches with a list of int_goals_df.
Any tips would be greatly appreciated ! Thank you !
EDIT - here is the code I'm using:
//get current GoalDefinitions by selected Goal
var currentGoalDefinition = MyAppAppContext.MyAppAppContextInstance.MyAppContext.GoalDefinitions.FirstOrDefault(
d => d.DLD_GoalDFID == interv.Goal.DLD_GoalenDFID);
// get current intervGoalDefinitions by GoalDefinition
var currentintervGoalDefinitions = MyAppAppContext.MyAppAppContextInstance.MyAppContext.intervGoalDefinitions.Where(
idd => idd.DLD_GoalDFID == currentGoalDefinition.DLD_GoalDFID).OrderBy(idd => idd.IDD_VolgNummer);
intervDefinitionCollection = new ObservableCollection<intervDefinition>(MyAppAppContext.MyAppAppContextInstance.MyAppContext.intervDefinitions.Where(i => i.IND_Categorie == intCategorie));
// filter intervGoalDefinitions by intervDefinitions
var intervDefinitionCollectionTemp = new ObservableCollection<intervDefinition>();
foreach (var currentintervGoalDefinity in currentintervGoalDefinitions)
{
var foundintervGoalDefinitySorted = intervDefinitionCollection.FirstOrDefault(
i => i.IND_intervDFID == currentintervGoalDefinity.IND_intervDFID);
if (foundintervGoalDefinitySorted != null)
intervDefinitionCollectionTemp.Add(foundintervGoalDefinitySorted);
}
intervDefinitionCollection = intervDefinitionCollectionTemp;
assuming NHibernate as ORM and int_goal is a subclass of goal
var results = from idd in session.Query<IntGoals>()
where idd.DlDNumber = 123456 && idd.Category.Id == 2
orderby idd.FollowNumber
select new { idd.Description, idd.Number };
context.int_goals_df.Join(context.goals_df, x => x.ind_number, x => x.number,
(x, y) => new
{
idd = x,
ind = y
})
.Where(x => x.idd.dld_number = 123456 && x.ind.categorie = 2)
.OrderBy(x => x.idd.follownumber)
.Select(x => new
{
x.ind.desc,
x.ind.number
});
quick go - think you need the join
var results = from idd in session.Query<int_goals_df>()
join ind in session.Query<goals_df>()
on idd.ind_number equals ind.ind_number
where idd.DlDNumber = 123456 && idd.Category.Id == 2
orderby idd.FollowNumber
select new { idd.Description, idd.Number };
I tend to use the sql syntax without implicit joins
/*Fields*/
SELECT ind.desc, ind.number
/*Tables*/
FROM int_goals_df idd
INNER JOIN goals_df ind
ON ind.number = idd.ind_number
/*Conditions*/
WHERE idd.dld_number = 123456
AND ind.categorie = 2
/*Order/Grouping*/
ORDER BY follownumber
You can see from Chris's answer this translates more easily to linq.

linq after groupby unable to get column values

I am getting data from multiple tables by joining and i want to group data on particular column value but after group by statement i can access my aliases and their properties. What mistake i am making?
public List<PatientHistory> GetPatientHistory(long prid)
{
using(var db = new bc_limsEntities())
{
List<PatientHistory> result =
(from r in db.dc_tresult
join t in db.dc_tp_test on r.testid equals t.TestId into x
from t in x.DefaultIfEmpty()
join a in db.dc_tp_attributes on r.attributeid equals a.AttributeId into y
from a in y.DefaultIfEmpty()
where r.prid == prid
group new {r,t,a} by new {r.testid} into g
select new PatientHistory
{
resultid = r.resultid,
bookingid = r.bookingid,
testid = r.testid,
prid = r.prid,
attributeid = r.attributeid,
result = r.result,
Test_Name = t.Test_Name,
Attribute_Name = a.Attribute_Name,
enteredon = r.enteredon,
Attribute_Type = a.Attribute_Type
}).ToList();
return result;
}
}
You're doing this wrong way. As been said by Jon after grouping the sequences with aliases r,t,a doesn't exist. After grouping you receive the sequence g with sequances of r,t,a in each element of g. If you want get one object from each group (for example most recent) you should try this:
List<PatientHistory> result =
(from r in db.dc_tresult
join t in db.dc_tp_test on r.testid equals t.TestId into x
from t in x.DefaultIfEmpty()
join a in db.dc_tp_attributes on r.attributeid equals a.AttributeId into y
from a in y.DefaultIfEmpty()
where r.prid == prid
group new {r,t,a} by new {r.testid} into g
select new PatientHistory
{
resultid = g.Select(x => x.r.resultid).Last(), // if you expect single value get it with Single()
// .... here add the rest properties
Attribute_Type = g.Select(x => x.a.Attribute_Type).Last()
}).ToList();
I appreciated this question so I thought I would add another potential usage case. I would like feedback on what the cleanest approach is to getting table information through a group operation so that I can project later in the select operation. I ended up combining what the OP did which is to pass objects into his group clause and then used the g.Select approach suggested by YD1m to get table information out later. I have a LEFT JOIN so I'm defending against nulls :
// SQL Query
//DECLARE #idCamp as Integer = 1
//
//select *,
//(select
//count(idActivityMaster)
//FROM tbActivityMasters
//WHERE dftidActivityCategory = A.idActivityCategory) as masterCount
//FROM tbactivitycategories A
//WHERE idcamp = #idCamp
//ORDER BY CategoryName
int idCamp = 1;
var desiredResult =
(from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group new {c, m} by new { m.dftidActivityCategory } into g
select new
{
idActivityCategory = g.Select(x => x.m == null ? 0 : x.m.dftidActivityCategory).First(),
idCamp = g.Select(x => x.c.idCamp).First(),
CategoryName = g.Select(x => x.c.CategoryName).First(),
CategoryDescription = g.Select(x => x.c.CategoryDescription).First(),
masterCount = g.Count(x => x.m != null)
}).OrderBy(o=> o.idActivityCategory);
desiredResult.Dump("desiredResult");
If I just use a basic group approach I get the results but not the extra column information. At least I can't find it once I group.
var simpleGroup = (from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
.OrderBy(o => o.CategoryName)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group m by m == null ? 0 : m.dftidActivityCategory into g
select new
{
// How do I best get the extra desired column information from other tables that I had before grouping
// but still have the benefit of the grouping?
// idActivityCategory = g.Select(x => x.m == null ? 0 : x.m.dftidActivityCategory).First(),
// idCamp = g.Select(x => x.c.idCamp).First(),
// CategoryName = g.Select(x => x.c.CategoryName).First(),
// CategoryDescription = g.Select(x => x.c.CategoryDescription).First(),
// masterCount = g.Count(x => x.m != null)
idActivityCategory = g.Key,
masterCount = g.Count(x => x != null)
});
simpleGroup.Dump("simpleGroup");
Please tear this up. I'm trying to learn and it just seems like I'm missing the big picture here. Thanks.
UPDATE : Cleaned up by moving the work into the group and making the select more straight forward. If I had known this yesterday then this would have been my original answer to the OP question.
int idCamp = 1;
var desiredResult =
(from c in tbActivityCategories
.Where(w => w.idCamp == idCamp)
from m in tbActivityMasters
.Where(m => m.dftidActivityCategory == c.idActivityCategory)
.DefaultIfEmpty() // LEFT OUTER JOIN
where c.idCamp == idCamp
group new { c, m } by new
{ idActivityCategory = m == null ? 0 : m.dftidActivityCategory,
idCamp = c.idCamp,
CateGoryName = c.CategoryName,
CategoryDescription = c.CategoryDescription
} into g
select new
{
idActivityCategory = g.Key.idActivityCategory,
idCamp = g.Key.idCamp,
CategoryName = g.Key.CateGoryName,
CategoryDescription = g.Key.CategoryDescription,
masterCount = g.Count(x => x.m != null)
}).OrderBy(o => o.idActivityCategory);
desiredResult.Dump("desiredResult");

Categories

Resources