DefaultIfEmpty() does not handle empty collections - c#

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

Related

Linq left join with group join

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.

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

Multiple Select and Join with LINQ and Lambda

How can I do this query using LINQ and LAMBDA ?
QUERY
Select san_negocio.imovel_id
,san_negocio.negocio_id
,san_imovel.credenciada_id
,san_proposta.proposta_id
,san_proposta.credenciada_id
from san_negocio
join san_proposta
on san_negocio.imovel_id = san_proposta.imovel_id
join san_imovel
on san_negocio.imovel_id = san_imovel.imovel_id
where san_negocio.credenciadacaptadora_id is null
and san_negocio.credenciadavendedora_id is null
and san_proposta.statusproposta_id = 2
I've tried:
var objetos = db.San_Negocio.Join(db.San_Proposta, a => a.Imovel_Id, b => b.Imovel_Id, (a, b) => new { San_Negocio = a, San_Proposta = b })
.Join(db.San_Imovel, a => a.San_Negocio.Imovel_Id, c => c.Imovel_Id, (a, c) => new { San_Negocio = a, San_Imovel = c })
.Where(a => a.San_Negocio.San_Negocio.CredenciadaCaptadora_Id == null && a.San_Negocio.San_Negocio.CredenciadaVendedora_Id == null)
.Select(a => new { a.San_Negocio.San_Negocio.Negocio_Id,
a.San_Negocio.San_Negocio.Imovel_Id,
a.San_Imovel.Credenciada_Id });
My doubt is in my Select. How can I call my San_Proposta table ?
You are hiding San_Proposta within a field called San_Negocio, so calling a.San_Negocio.San_Proposta will access it, but I recommend writing your joins in a way that fields aren't nested, like this:
var objetos = db.San_Negocio
.Join(db.San_Proposta,
a => a.Imovel_Id,
b => b.Imovel_Id,
(a, b) => new { San_Negocio = a, San_Proposta = b })
.Join(db.San_Imovel,
a => a.San_Negocio.Imovel_Id,
c => c.Imovel_Id,
(a, c) => new { a.San_Negocio, a.San_Proposta, San_Imovel = c })
.Where(a => a.San_Negocio.CredenciadaCaptadora_Id == null &&
a.San_Negocio.CredenciadaVendedora_Id == null)
.Select(a => new
{
a.San_Negocio.Negocio_Id,
a.San_Negocio.Imovel_Id,
a.San_Proposta.San_Proposta_Id,
a.San_Imovel.Credenciada_Id
});
Here is a proper linq statement:
from neg in db.san_negocio
join prop in san_proposta
on neg.imovel.id equals prop.imovel_id
join imo in san_imovel
on neg.imovel_id = imo.imovel_id
where neg.credenciadacaptadora_id == null &&
neg.credenciadavendedora_id == null &&
prop.statusproposta_id == 2
select new {
ImovelID = neg.imovel_id,
NegocioID = neg.negocio_id,
Imo_CredenciadaID = imo.credenciada_id,
PropostaID = prop.proposta_id
Prop_CredenciadaID = prop.credenciada_id
};
This will create an IQueryable of anonymous objects with the listed properties above.

How to use LINQ with calculated properties in a single pass?

I'd like to make a LINQ query, extracting dynamic properties (calculated fields) of my entities in a single pass, without get the error "The specified type member 'EntityKey' is not supported in LINQ to Entities".
Here is the only working way I found, but I am sure there are better and more elegant methods:
var q = (from i in
(from x in context.Tickets
select new { x.OperatoreID, x.DataObiettivo })
group i by new { i.OperatoreID } into g
select new vmOperatoreDateObiettivo
{
OperatoreID = g.Key.OperatoreID,
NOperatore = "", // field value to be updated...
DataObiettivo = g.Max(d => d.DataObiettivo),
MinutiAllaScadenza = 0, // field to be updated...
Alert = "" // field value to be updated...
}).ToList();
// Here I update my fields with a second pass....
foreach (vmOperatoreDateObiettivo e in q)
{
string nome = context.Operatori
.Where(t => t.OperatoreID == e.OperatoreID)
.First().CognomeNomePuntato.ToString();
e.NOperatore = nome;
int minscad = context.Tickets
.Where(t => t.OperatoreID == e.OperatoreID).AsEnumerable().Min(a => a.MinutiAllaScadenza);
e.MinutiAllaScadenza = minscad;
string sev = context.Tickets
.Where(t => t.OperatoreID == e.OperatoreID).AsEnumerable().Min(a => a.Alert);
e.Alert = sev;
}
Thanks in advance!
Try adding a let clause to your query and define calculated field, like so:
var q = (from i in
(from x in context.Tickets
select new { x.OperatoreID, x.DataObiettivo })
group i by new { i.OperatoreID } into g
let nOperatore = context.Operatori
.Where(t => t.OperatoreID == e.OperatoreID)
.First().CognomeNomePuntato.ToString() &&
minutialla = context.Tickets
.Where(t => t.OperatoreID == e.OperatoreID)
.AsEnumerable().Min(a => a.MinutiAllaScadenza) &&
alert = context.Tickets
.Where(t => t.OperatoreID == e.OperatoreID)
.AsEnumerable().Min(a => a.Alert)
select new vmOperatoreDateObiettivo
{
OperatoreID = g.Key.OperatoreID,
NOperatore = nOperatore,
DataObiettivo = g.Max(d => d.DataObiettivo),
MinutiAllaScadenza = minutialla,
Alert = alert
}).ToList();

Categories

Resources