Convert SQL to LINQ Troubles - c#

I have been stuck on this for an embarrassing day... can't seem to convert this to linq. My issue also is that Attendee can be null.
select c.activityId, count(distinct b.attendeeId)
from Attendee a, sponsor_activity c
left outer join sponsor_attendance b
on c.ActivityId = b.ActivityId
where a.RegistrationId = 62
AND c.SponsorLevelId = 2
group by c.activityId
So far I have this code... but I am not getting distinct values
var activity_count = (from c in db.Sponsor_Activitys
where c.SponsorLevelId == pledgelvl
from a in db.Attendees.DefaultIfEmpty()
where a.RegistrationId == registration
select new { Activityid = c.ActivityId, NumAttending = db.Sponsor_Attendances.Count(x => x.ActivityId == c.ActivityId) })
.ToList();
Sponsor_Attendance
AttendanceId
AttendeeId
ActivityId
Sponsor_Activity
ActivityId
SponsorLevelId
Attendee
AttendeeId
RegistrationId
Returns:
## ActivityID ## ## NumAttending ##
2 4
3 0
4 2
2 4
3 0
4 2
2 4
3 0
4 2
Currently there are 3 attendees that have a registrationid that matches... so this is why it is repeated 3 times in the output.

First, it helps if your original queries are readable. :)
Query:
SELECT c.activityId
, COUNT(DISTINCT b.attendeeId)
FROM Attendee a
, sponsor_activity c
LEFT OUTER JOIN sponsor_attendance b
ON c.ActivityId = b.ActivityId
WHERE a.RegistrationId = 62 AND
c.SponsorLevelId = 2
GROUP BY c.activityId;
Linq:
var activity_count = (from activity in db.Sponsor_Activitys
where activity.SponsorLevelId == pledgelvl
from attendee in db.Attendees.DefaultIfEmpty()
where attendee.RegistrationId == registration
select new
{
Activityid = activity.ActivityId,
NumAttending = db.Sponsor_Attendances.Count(x => x.ActivityId == activity.ActivityId)
}).ToList();
My answer:
var query = from activity in db.Sponsor_Activitys
// Left outer join onto sponsor_attendances
join attendance in db.Sponsor_Attendances
on activity.ActivityId equals attendance.ActivityId into g
from q in g.DefaultIfEmpty()
join attendee in db.Attendees
on q.AttendeeId equals attendee.AttendeeId
where attendee.RegistrationId == registration &&
activity.SponsorLevelId == pledgelvl
select new
{
Activityid = activity.ActivityId,
NumAttending = db.Sponsor_Attendances.Count(x => x.ActivityId == activity.ActivityId)
}
Given the cartesian join (typically bad!), this might be a better example on just executing SQL rather than trying to convert to Linq.

Related

Convert multiple PARTITION BY to LINQ

EF Core does not have a SqlQuery() method yet (epic fail) so I have no way to run a query/stored proc on my database to get "summary" data. Here is a summary query I have with multiple PARTITION BYs that I need to convert to LINQ:
SELECT
s.Name,
SUM(CASE WHEN pc.WorkflowStateId <> 99 THEN 1 ELSE 0 END)
OVER (PARTITION BY s.SiteId) 'RegisteredCount',
SUM(CASE WHEN pc.WorkflowStateId = 99 THEN 1 ELSE 0 END)
OVER (PARTITION BY s.SiteId) 'ScreenFailedCount'
FROM Sites s
JOIN Patients p ON p.SiteId = s.SiteId
JOIN PatientCycles pc ON pc.PatientId = p.PatientId
Sites, Patients, and PatientCycles are DbSets.
How can I convert this to a C# LINQ query?
UPDATE
This is the solution I came up with:
var summaries = from site in context.Sites
let registered = (from pc in context.PatientCycles
where site.SiteId == pc.Patient.SiteId && pc.WorkflowStateId != WorkflowStateType.Terminated
select pc).Count()
let terminated = (from pc in context.PatientCycles
where site.SiteId == pc.Patient.SiteId && pc.WorkflowStateId == WorkflowStateType.Terminated
select pc).Count()
select new SiteSummary { Site = site, RegisteredCount = registered, ScreenFailedCount = terminated };
Well an alternative in Linq would be something like this:
var query= from s in context.Sites
join p in context.Patiens on s.SiteId equals p.SiteId
join pc in context.PatientCycles on p.PatiendId equals p.PatiendId
group pc.WorkflowStateId by new{s.SiteId,s.Name} into g
select new {Name=g.Key.Name,
RegisteredCount=g.Sum(e=>e!=99?1:0),
ScreenFailedCount=g.Sum(e=>e==99?1:0)
}
Update
To avoid that kind of problem use nav. properties:
var query= from s in context.Sites
select t new SiteSummary
{
SiteId = s.SiteId,
Name = s.Name,
Code = s.Code,
RegisteredCount =s.Patiens.SelectMany(e=>e.PatientCycles.Select(y=>y.WorkflowStateId ))
.Sum(x => x!= WorkflowStateType.Terminated ? 1 : 0),
ScreenFailedCount = s.Patiens.SelectMany(e=>e.PatientCycles.Select(y=>y.WorkflowStateId ))
.Sum(x => x== WorkflowStateType.Terminated ? 1 : 0)
};
Also I suggest to initialize the collection navigation properties in entity constructors:
public class Sites
{
//...
public virtual ICollection<Patient> Patients{get;set;}
public Sites()
{
Patients=new List<Patient>();
}
}

group by in linq doesn't show the expected result

I have these tables :
TestPackagejoint(id,testpackageid,jointid)
Joint(id,endid)
I create a joint between testpackage and joint and joint based on jointid,id .
I want to group the value based on endid and testpackageid,and count of endid or example :
testpackageid endid count
1 2 3
4 2 1
1 3 2
So i write this query
var q = (from i in _ctx.TestPackageJoints
where i.TestPackageId == Id
join joint1 in _ctx.Joints on i.JointId equals joint1.Id
group new {joint1.EndId,i.TestPackageId} by new { i, joint1}
into g1
select new
{
testpackid = g1.Key.i.TestPackageId,
Count = g1.Count(),
endid = g1.Key.joint1.EndId
}).ToList();
But the result :
You must understand what want to get. You want to get testpackid and endid, so it's correct to group them. You also want to group by testpackid and endid, why do you group by new {i, ...} here.
Try
group new {joint1.EndId,i.TestPackageId} by new {joint1.EndId,i.TestPackageId}
Or
join ...
let item = new {joint1.EndId,i.TestPackageId}
group item by item
Your query ought to be:
var query =
from tpj in _ctx.TestPackageJoints
join j in _ctx.Joints on tpj.JointId equals j.Id
where tpj.TestPackageId == id
group 1 by new { tpj.TestPackageId, j.EndId } into g
select new
{
g.Key.TestPackageId,
g.Key.EndId,
Count = g.Count(),
};
The group by clause is in the form group [object] by [key]. You included a TestPackageJoint object as part of the key which likely doesn't define equality so it produced a group for every pairing.

linq join incorrect syntax error

I am using LINQ query to get the rows with certain conditions. Here is the query.
var query = from v in dt1.AsEnumerable()
join c in dt2.AsEnumerable() on v.Field<int>("ID") equals c.Field<int>("ID")
where v.Field<string>("col1").Equals("abcd")
&& (c.Field<string>("col1").Equals("8776") || c.Field<string>("col1").Equals("8775"))
select new
{
ok = (from a in v where v.Field<string>("stah").Equals("1") select a).count(),
ok1 = (from a in v where v.Field<string>("stah").Equals("2") select a).count(),
ok2 = (from a in v where v.Field<string>("stah").Equals("3") select a).count()
};
The error is present in
ok = (from a in v where v.Field<string>("stah").Equals("1") select a).count()
The error is
could not find an implementation of the query pattern for source type
'system.data.DataRow'. 'Where' not found
Sample Input :
dt1
iD col1 stah
1 4567 1
2 8748 2
3 3487 3
4 8776 1
dt2
iD col1
1 4754
2 4576
Output
Get count of all rows where stah=1 && dt2.col1='4754'
But I cannot get it working. What is the correct syntax for this ?
If I have understood you correctly, then this is what you need:-
var query = dt1.AsEnumerable()
.Where(x => x.Field<int>("stah") == 1
&& dt2.AsEnumerable()
.Any(z => z.Field<int>("Id") == x.Field<int>("Id")
&& z.Field<string>("Col1") == "4754")
).Count();
#HarshitShrivastava mentioned that my previous attempt at the query didn't take into account all the where conditions.
How about this version using a mix of Linq query and Linq Lambda:
var query = from dataRows1 in dt1.AsEnumerable().Where(r => r.Field<string>("col1").Equals("abcd"))
join dataRows2 in dt2.AsEnumerable().Where(r => r.Field<string>("col1").Equals("8776") || r.Field<string>("col1").Equals("8775"))
on dataRows1.Field<int>("ID") equals dataRows2.Field<int>("ID") into b
select new
{
id = dataRows1.Field<int>("ID"),
ok = (from a in b where a.Field<string>("stah").Equals("1") select a).Count(),
ok1 = (from a in b where a.Field<string>("stah").Equals("2") select a).Count(),
ok2 = (from a in b where a.Field<string>("stah").Equals("3") select a).Count()
};
Note: I included the ID field in the projected output just for verifying the results. Remove as needed.

Entity Framework multiple join types with dates, group by and count

I have a chat system that consist of 3 tables: Users, RoomJoins (private conversation room that you have joined) and Chats (list of chat messages with a timestamp).
I want to get a list of all private rooms for a given user, followed by the number of new chat messages he has received since his last login.
The sql query that works (maybe can be improved, but certainly works) is:
select a.roomid, b.userid, m.firstpicurl, count(p.roomid) as cuenta from roomjoin a
inner join roomjoin b
on b.roomid=a.roomid and a.userid<>b.userid
inner join myuser m on m.id=b.userid
left join
chat p on p.roomid=a.roomid and p.sentwhen > a.lastseen
where a.userid=45 and a.active=1
group by a.roomid, b.userid, m.firstpicurl
that basically says: give me all roomids (private conversations) for userid=45, also the userid of the person that sent me the message, his picture and the number of chat messages where sentwhen > user.lastseen
the result will be something like
roomid userid firstpicurl cuenta
1 43 http://... 3
2 37 http://... 0
meaning the userid 43 has sent you 3 messages since your last login, while the user 37 didn't sent you anything new
Now, I try to translate this into EF and I kind of got it working, but the problem is that I can't find a way to query using the sentwhen > lastseen date format because it doesn't allow for that. And if I try with Where clauses, I never get the correct answer. Here is my existing try (without sentwhen > lastseen)
from a in db.RoomJoins.Where(c => c.userid == u.id && c.active == true).OrderBy(c => c.roomid)
from b in db.RoomJoins.Where(fp => fp.roomid == a.roomid && fp.userid != a.userid)
from m in db.Users.Where(m => b.userid == m.id)
join p in db.Chats on a.roomid equals p.roomid into j1
from j2 in j1.DefaultIfEmpty()
group j2 by new { a.roomid, b.userid, m.firstpicurl } into g
select new
{
roomid = g.Key.roomid,
userid = g.Key.userid,
firstpicurl = g.Key.firstpicurl,
count = g.Count()
};
so my code seems to work but it has 2 problems:
1) it doesn't take into account the timestamp, I just want a count of messages after my lastseen
2) I get a count of 1 when it should be 0. So I would get something like this
roomid userid firstpicurl cuenta
1 43 http://... 3
2 37 http://... 1 <-- this should be 0
anyone knows how to achieve what i'm looking for?
First answer:
This one seems to work but it looks very complex. Is there a way to make simpler?
from a in db.RoomJoins.Where(c => c.userid == 45 && c.active == true)
from b in db.RoomJoins.Where(fp => fp.roomid == a.roomid && fp.userid != a.userid)
from m in db.Users.Where(m => b.userid == m.id)
from p in db.Chats.Where(p => p.roomid==a.roomid &&
p.sentwhen > a.lastseen).DefaultIfEmpty()
select new
{
roomid = a.roomid,
userid = b.userid,
firstpicurl = m.firstpicurl,
cid = p.Id
} into j1
group j1 by new { j1.roomid, j1.userid, j1.firstpicurl } into g
select new
{
roomid = g.Key.roomid,
userid = g.Key.userid,
firstpicurl = g.Key.firstpicurl,
count = g.Count(e => e.cid!=null)
};
join p in db.Chats.Where(x => x.sentwhen > a.lastseen)
on a.roomid equals p.roomid into j1

Outer join query using LINQ to Entities [duplicate]

This question already has answers here:
Linq-to-Entities Join vs GroupJoin
(3 answers)
Closed 1 year ago.
There are 0 to n departments in my company, 0 to n offices in 1 department, and 0 to n emplyees in 1 office.Now I need a query using linq to list emplyees's average age by department, if nobody in a department then default average is 0.
code is below:
DataContext ctx = new DataContext();
var q0 = from d in ctx.Departments
join o in ctx.Offices on d.Id equals o.DepartmentId
join e in ctx.Employees on o.Id equals e.OfficeId
group e by d into de
select new {
DepartmentId = de.Key.Id,
AverageAge = de.Count() == 0 ? 0 : de.Average(e => e.Age),
};
var q1 = from d in ctx.Departments
join de in q0 on d.Id equals de.DepartmentId into des
from de in des.DefaultIfEmpty()
select new
{
DepartmentName = d.Name,
AverageAge = de == null ? 0 : de.AverageAge
};
var result = q1.ToList();
foreach (var item in result)
{
Console.WriteLine("{0}-{1}", item.DepartmentName, item.AverageAge);
}
ctx.Dispose();
But how to combine q0 and q1 to one query?
Were you meaning something along the lines of:
var newQ2 = from d in ctx.Departments
outer left join o in ctx.Offices on d.Id equals o.DepartmentId
outer left join e in ctx.Employees on o.Id equals e.OfficeId
group e by d into de
select new {
DepartmentId = de.Key.Id,
AverageAge = de.Count() == 0 ? 0 : de.Average(e => e.Age),
};
Changed to:
var newQ2 = from d in ctx.Departments
join o in ctx.Offices on d.Id equals o.DepartmentId
join e in ctx.Employees on o.Id equals e.OfficeId
group e by d into de.DefaultIfEmpty()
select new {
DepartmentId = de.Key.Id,
DepartdentName = select d.Name from d where d.id = de.Key.Id,
AverageAge = de.Count() == 0 ? 0 : de.Average(e => e.Age),
};
Addendum: I would use a sub-select to match up the extra name, not knowing your db layout I have improvised from your code, but you could make it more efficient and have a multipart join based on sub-selects as well. Sorry I cant test this code out at work, I can approximate fairly well, but would need some more info on where your department names are located if you need a more detailed answer:) I have changed the outer left joins back to joins, sorry I forgot in C# with linq you can use DefaultIfEmpty() to cause outer left join behaviour in code.
An outer left join will return nulls where there are no corresponding values, but will allow returns on any parts that do have a corresponding value. Join however will not return any null entries which I suspect is why you had the two queries?
The only caveat on the query I have presented is that you will need to infill any values you require before you use them if they are nulls, for example DepartmentId will need some logic to populate it in case DE is null.
Thank you all,I have got the answer:
var q1 =
from d in ctx.Departments
from o in ctx.Offices.Where(o => o.DepartmentId == d.Id).DefaultIfEmpty()
from e in ctx.Employees.Where(e => e.OfficeId == o.Id).DefaultIfEmpty()
group e by d into de
select new {
DepartmentName = de.Key.Name,
AverageAge = de.Average(e => e == null ? 0 : e.Age),
};

Categories

Resources