LEFT OUTER JOIN in Linq - How to Force - sql

I have a LEFT OUTER OUTER join in LINQ that is combining with the outer join condition and not providing the desired results. It is basically limiting my LEFT side result with this combination. Here is the LINQ and resulting SQL. What I'd like is for "AND ([t2].[EligEnd] = #p0" in the LINQ query to not bew part of the join condition but rather a subquery to filter results BEFORE the join.
Thanks in advance (samples pulled from LINQPad) -
Doug
(from l in Users
join mr in (from mri in vwMETRemotes where met.EligEnd == Convert.ToDateTime("2009-10-31") select mri) on l.Mahcpid equals mr.Mahcpid into lo
from g in lo.DefaultIfEmpty()
orderby l.LastName, l.FirstName
where l.LastName.StartsWith("smith") && l.DeletedDate == null
select g)
Here is the resulting SQL
-- Region Parameters
DECLARE #p0 DateTime = '2009-10-31 00:00:00.000'
DECLARE #p1 NVarChar(6) = 'smith%'
-- EndRegion
SELECT [t2].[test], [t2].[MAHCPID] AS [Mahcpid], [t2].[FirstName], [t2].[LastName], [t2].[Gender], [t2].[Address1], [t2].[Address2], [t2].[City], [t2].[State] AS [State], [t2].[ZipCode], [t2].[Email], [t2].[EligStart], [t2].[EligEnd], [t2].[Dependent], [t2].[DateOfBirth], [t2].[ID], [t2].[MiddleInit], [t2].[Age], [t2].[SSN] AS [Ssn], [t2].[County], [t2].[HomePhone], [t2].[EmpGroupID], [t2].[PopulationIdentifier]
FROM [dbo].[User] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[MAHCPID], [t1].[FirstName], [t1].[LastName], [t1].[Gender], [t1].[Address1], [t1].[Address2], [t1].[City], [t1].[State], [t1].[ZipCode], [t1].[Email], [t1].[EligStart], [t1].[EligEnd], [t1].[Dependent], [t1].[DateOfBirth], [t1].[ID], [t1].[MiddleInit], [t1].[Age], [t1].[SSN], [t1].[County], [t1].[HomePhone], [t1].[EmpGroupID], [t1].[PopulationIdentifier]
FROM [dbo].[vwMETRemote] AS [t1]
) AS [t2] ON ([t0].[MAHCPID] = [t2].[MAHCPID]) AND ([t2].[EligEnd] = #p0)
WHERE ([t0].[LastName] LIKE #p1) AND ([t0].[DeletedDate] IS NULL)
ORDER BY [t0].[LastName], [t0].[FirstName]

I'm not sure if it will change the result set with "AND ([t2].[EligEnd] = #p0" as part of the subquery rather than the join condition. One thing I like to do with complex queries might help you here. I like to break them into smaller queries before combining them. The deferred execution of LINQ lets us do multiple statements with one eventual call to the database. Something like this:
var elig = from mri in vwMETRemotes
where met.EligEnd == Convert.ToDateTime("2009-10-31")
select mri;
var users = from l in Users
where l.LastName.StartsWith("smith")
where l.DeletedDate == null
var result = from l in users
join mr in elig on l.Mahcpid equals mr.Mahcpid into lo
from g in lo.DefaultIfEmpty()
orderby l.LastName, l.FirstName
select g
Breaking it down like that can make it easier to debug, and perhaps it can tell LINQ better what you intend.

Code ended up looking like this. RecodePopulation and RecordRegistration are just methods to translate values from the query.
var elig = from mri in db.MetRemote
where mri.EligEnd == Convert.ToDateTime(ConfigurationManager.AppSettings["EligibilityDate"])
orderby mri.EligEnd
select mri;
var users = from l in db.Users
where l.LastName.StartsWith(filter)
where l.DeletedDate == null
select l;
var results = (from l in users
join m in elig on l.MahcpId equals m.MAHCPID into lo
from g in lo.DefaultIfEmpty()
orderby l.LastName, l.FirstName
select new UserManage()
{
Username = l.Username,
FirstName = l.FirstName,
LastName = l.LastName,
DateOfBirth = l.DOB,
Gender = l.Gender,
Status = RecodePopulation(g.Population, l.CreatedDate),
UserId = l.Id,
WellAwardsRegistered = RecodeRegistration(l.Id, 1)
}).Distinct().OrderBy(a => a.LastName).ThenBy(n => n.FirstName).Skip((currentPage - 1) * resultsPerPage).Take(resultsPerPage);

Related

linq with right and left join

We are incorporating entity framework in my application. I have converted lot of simple stored procedures into linq queries but this one is giving me trouble and I am not sure where I am going wrong. My brain has turned into jelly and I cannot make it work. I will really appreciate your help so please bear with me.
My SQL is:
SELECT ...
FROM tblProjectAssignment
INNER JOIN tblProjectAssignmentCollection
ON tblProjectAssignment.AssignmentID = tblProjectAssignmentCollection.AssignmentID
RIGHT OUTER JOIN tblCEQRPhaseIIUsers
ON tblProjectAssignment.UserID = tblCEQRPhaseIIUsers.UserID
LEFT OUTER JOIN tblCEQRAccessUserRole
ON tblCEQRPhaseIIUsers.UserRoleTypeID = tblCEQRAccessUserRole.UserRoleTypeID
I need help converting the above to a linq query. I know DefaultIfEmpty() is used for outer joins, but the results that I get when I run the query are incorrect. So, what I need is tblCEQRPhaseIIUsers to get all results independent of the corresponding results in other table. The below query is behaving as all tables has inner joins.
var query = from PATable in db.tblProjectAssignments
from PACTable in db.tblProjectAssignmentCollections.Where(la => la.AssignmentID == PATable.AssignmentID)
from UserTable in db.tblCEQRPhaseIIUsers.Where(la => la.UserID == PATable.UserID).DefaultIfEmpty()
from UserRoleTable in db.tblCEQRAccessUserRoles.Where(la => la.UserRoleTypeID == UserTable.UserRoleTypeID).DefaultIfEmpty()
where (UserTable.FirstName.ToLower().Contains(search.FirstName.Trim().ToLower()) || search.FirstName == null)
where (UserTable.LastName.ToLower().Contains(search.LastName.Trim().ToLower()) || search.LastName == null)
where (PACTable.CEQRNumber.ToLower().Contains(search.CEQRNumber.Trim().ToLower()) || search.CEQRNumber == null)
where (PACTable.ProjectName.ToLower().Contains(search.ProjectName.Trim().ToLower()) || search.ProjectName == null)
where (PATable.IsActive == true)
where (UserTable.IsActive == true)
select new
{
AssignmentID = PATable.AssignmentID,
UserID = PATable.UserID,
FirstName = UserTable.FirstName,
MiddleName = UserTable.MiddleName ?? string.Empty,
LastName = UserTable.LastName,
UserRole = UserRoleTable.UserRoleName,
CEQRNumber = PACTable.CEQRNumber,
ProjectName = PACTable.ProjectName,
CollectionID = PACTable.CollectionID,
EmailAddress = UserTable.EmailAddress,
AssignmentIsActive = PACTable.IsActive
};
The problem is you can't do right joins in linq. The good news is you can rewrite your query to avoid using the right join. You do this by rearranging the tables and using left joins instead.
Currently you are doing this:
select ...
from [MainTable] m
inner join [InnerTable] i on m.Num = i.Num
right join [RightTable] r on m.Num = r.Num
left join [LeftTable] l on r.Num = l.Num
That should be equivalent to this:
select...
from [RightTable] r
left join [LeftTable] l on r.Num = l.Num
left join [MainTable] m on m.Num = r.Num
left join [InnerTable] i on m.Num = i.Num
Now you have a query without right joins, you should be able to write the linq query

Why LINQ Count() returning multiple rows instead one?

I would like to translate the following SQL into LINQ:
select count(p.ID) as NumPosts,
count(t.Trustee_ID)as TrusteePost,
count(pat.ID)as PatientPost,
count(s.ID) as SpecialistPost
from [dbo].[Posts] as p
left join [dbo].[Trusteehips] as t
on p.Autor_ID = t.Trustee_ID
left join [dbo].[Patients] as pat
on p.Autor_ID = pat.ID
left join [dbo].[Specialists] as s
on p.Autor_ID = s.ID
where p.Deleted = 0
I I've tried this:
var res = from p in context.Posts
join t in context.Trusteeships
on p.Autor.ID equals t.Trustee.ID into tGroup
join pat in context.Patients
on p.Autor.ID equals pat.ID into patGroup
join s in context.Specialists
on p.Autor.ID equals s.ID into sGroup
select new NumUserPosts
{
//CountAllPosts = ?
TrusteePost = tGroup.Count(),
PatientPost = patGroup.Count(),
SpecialistPost = sGroup.Count()
};
But result is this:
1 0 0
0 0 1
0 0 1
0 1 0
and etc.
I expect result
TrusteePost PatientPost SpecialistPost
1000 2000 3000
Why when i try to count group return this result?
SQL query is correct. I would like to translate into LINQ.
The query returns 0 or 1 records per joined Trustee, etc. because you outer join by the unique primary key. So a join into (which is a GroupJoin in fluent syntax) produces a group of 0 or 1 records. If you run the generated SQL query and view the raw query result you'd probably understand better what's going on.
The problem is, there is no LINQ equivalent for count(t.Trustee_ID), etc. Therefore it's impossible to do what you want in one query without "hacking".
Hacking it into one query could be done like so:
(from p in context.Posts.Take(1)
select new
{
TrusteePost = context.Posts
.Count(p1 => context.Trusteeships.Any(x => x.ID == p1.Autor.ID)),
PatientPost = context.Posts
.Count(p2 => context.Patients.Any(x => x.ID == p2.Autor.ID)),
SpecialistPost = context.Posts
.Count(p3 => context.Specialists.Any(x => x.ID == p3.Autor.ID))
})
.AsEnumerable()
.Select(x => new NumUserPosts
{
CountAllPosts = x.TrusteePost + x.PatientPost + x.SpecialistPost,
x.TrusteePost,
x.PatientPost,
x.SpecialistPost
}
The SQL query will be much more elaborate than the original SQL (for example, it involves cross joins), but it will probably still perform pretty well. AsEnumerable prevents the second part from being executed as SQL, which would bloat the SQL statement even more. It simply runs in memory.
I consider this a hack because the first part, context.Posts.Take(1) doesn't really have any meaning, it's only there to serve as a wrapper for the three separate queries. It's poor man's query packaging.
It looks like you're doing group joins instead of left outer joins (see this page).
A left outer join looks more like:
var res = from p in context.Posts
join t in context.Trusteeships
on p.Autor.ID equals t.Trustee.ID into tGroup
from tJoin in tGroup.DefaultIfEmpty()
join pat in context.Patients
on p.Autor.ID equals pat.ID into patGroup
from patJoin in patGroup.DefaultIfEmpty()
join s in context.Specialists
on p.Autor.ID equals s.ID into sGroup
from sJoin in sGroup.DefaultIfEmpty()
select ...
Unfortunately, it doesn't seem that Linq can create a query to count elements in each column.
If you don't mind using multiple queries, you could count each separately, for example:
var trusteePost = (from p in context.Posts
join t in context.Trusteeships on p.Autor.ID equals t.Trustee.ID
select t).Count()

Convert LINQ query to Sqlquery

I need help to convert this query from LINQ syntax into SQLServer query syntax:
var t1 = (from p in db.Varors join
op in db.OrderVarors on p.id equals op.IdVara
where op.IdOrder == OrderId
select p).ToList();
Hope this helps:-
SELECT v.* from Varors v
JOIN ordervarors ov on v.id = ov.id
where ov.idOrder == #OrderId
You may want to choose individual columns instead of star but this should work.
select *
from dbo.Varors as P
join dbo.OrderVarors as OP
on p.id equals op.IdVara
where op.IdOrder = OrderId
this is a simple join - try this :
select Varors.*
from Varors
join OrderVarors on Varors.id = OrderVarors.IdVara
where OrderVarors.IdOrder = #OrderId

How to convert multiple SQL left joins to Linq-To-SQL

I have a user table, a user_items table, a user_to_group table and a group table.
How would I convert the following SQL query to the correct Linq-to-sql query?
select user.username, user.email, user_items.item_number, item_types.desc, [group].name from user
left join user_items on user.user_id = user_items.user_id
left join item_types on user_items.item_type_id = item_types.item_type_id
left join user_to_group on user.user_id = user_to_group.user_id
left join [group] on user_to_group.group_id = [group].group_id
I have tried using group joins and joining with DefaultIfEmpty, but I'm unable to return the exact same results as my SQL query.
Thank you.
#ChrisF,
Sure, I could break it down into:
select user.username, user.email, user_items.item_number from user
left join user_items on user.user_id = user_items.user_id
Which I have in linq2sql as:
var users = (from u in db.users
join ui in db.user_items on u.user_id equals ui.user_id into useritems from ui in useritems.DefaultIfEmpty()
select new { user = u, item_number = useritems == null ? string.Empty : useritems.FirstOrDefault().item_number});
However, it still doesn't return all the results it should, and I can't see where I'm going wrong.
You will need to look at the join ... into ... construct.
Here is an example on MSDN
... Something else to consider ...
LINQ will allow you to turn your simple table structure into a complex hierarchy. Another option beyond the left joins would be nested queries. Maybe something like this...
var selected = from user in users
select new
{
user,
items = from item in user_items
where item.user_id == user.user_id
join it in item_types
on item.item_type_id equals it.item_type_id
select it,
groups = from ug in user_to_groups
where ug.user_id == user.user_id
join #group in groups
on ug.group_id equals #group.group_id
select #group.name,
};
I found my problem, I was selecting
useritems.FirstOrDefault().item_number
Which is what I selected into (and FirstOrDefault!), instead of what I used for my join:
ui.item_number
So my complete linq query is
var users = (from u in db.users
join ug in db.user_to_groups on p.user_id equals pg.user_id into pgrp
from ug in pgrp.DefaultIfEmpty()
join g in db.groups on pg.group_id equals g.group_id into groups
from g in groups.DefaultIfEmpty()
join ui in db.user_items on u.user_id equals ui.user_id into useritems
from ui in useritems.DefaultIfEmpty()
select new { user = u, item_number = ui == null ? string.Empty : ui.item_number, usergroup = g == null ? string.Empty : g.name});
Thanks for the help!
You query could be written more tersely if you define some associations between your objects:
from u in db.Users
from ug in u.User_To_Groups.DefaultIfEmpty()
let g = ug.Group
from ui in u.UserItems.DefaultIfEmpty()
select new
{
user = u,
item_number = ui == null ? string.Empty : ui.item_number,
usergroup = g == null ? string.Empty : g.name
});

Optimize Group By in LINQ to Entities

I have this query in LINQ to Entities.
var query = (from s in db.ForumStatsSet
where s.LogDate >= date1 && s.LogDate <= date2
group s by new { s.Topic.topicID, s.Topic.subject, s.Topic.Forum.forumName, s.Topic.datum, s.Topic.Forum.ForumGroup.name, s.Topic.Forum.forumID } into g
orderby g.Count() descending
select new TopicStatsData
{
TopicId = g.Key.topicID,
Count = g.Count(),
Subject = g.Key.subject,
ForumGroupName = g.Key.name,
ForumName = g.Key.forumName,
ForumId = g.Key.forumID
});
I know it is kind of an "Evil" query but it is only used in a admin interface. But the SQL it generated is absolutely horrifying. Have a look at this baby.
exec sp_executesql N'SELECT TOP (50)
[Project6].[C1] AS [C1],
[Project6].[TopicId] AS [TopicId],
[Project6].[C4] AS [C2],
[Project6].[subject] AS [subject],
[Project6].[name] AS [name],
[Project6].[forumName] AS [forumName],
[Project6].[C2] AS [C3]
FROM ( SELECT
[Project5].[TopicId] AS [TopicId],
[Project5].[subject] AS [subject],
[Project5].[forumName] AS [forumName],
[Project5].[name] AS [name],
1 AS [C1],
CAST( [Project5].[forumID] AS int) AS [C2],
[Project5].[C1] AS [C3],
[Project5].[C2] AS [C4]
FROM ( SELECT
[Project4].[TopicId] AS [TopicId],
[Project4].[forumID] AS [forumID],
[Project4].[subject] AS [subject],
[Project4].[forumName] AS [forumName],
[Project4].[name] AS [name],
[Project4].[C1] AS [C1],
(SELECT
COUNT(cast(1 as bit)) AS [A1]
FROM [dbo].[tForumStats] AS [Extent14]
LEFT OUTER JOIN [dbo].[tTopic] AS [Extent15] ON [Extent14].[TopicId] = [Extent15].[topicID]
LEFT OUTER JOIN [dbo].[tForum] AS [Extent16] ON [Extent15].[forumID] = [Extent16].[forumID]
LEFT OUTER JOIN [dbo].[tForum] AS [Extent17] ON [Extent15].[forumID] = [Extent17].[forumID]
LEFT OUTER JOIN [dbo].[tForum] AS [Extent18] ON [Extent15].[forumID] = [Extent18].[forumID]
LEFT OUTER JOIN [dbo].[tForumGroup] AS [Extent19] ON [Extent18].[forumGroupID] = [Extent19].[forumGroupID]
LEFT OUTER JOIN [dbo].[tForum] AS [Extent20] ON [Extent15].[forumID] = [Extent20].[forumID]
LEFT OUTER JOIN [dbo].[tForumGroup] AS [Extent21] ON [Extent20].[forumGroupID] = [Extent21].[forumGroupID]
WHERE ([Extent14].[LogDate] >= #p__linq__25) AND ([Extent14].[LogDate] = #p__linq__25) AND ([Extent6].[LogDate] = #p__linq__25) AND ([Extent1].[LogDate]
I do not as anyone to explain that query but it would be great to get some tips on how to optimze the query so that it just do a simple regular join. Something like this works as fine if I write the SQL myself.
SELECT COUNT(*) AS NumberOfViews, s.topicid AS topicId, t.subject AS TopicSubject, g.[name] AS ForumGroupName, f.forumName AS ForumName
FROM tForumStats s
join tTopic t on s.topicid = t.topicid
join tForum f on f.forumid = t.forumid
JOIN tForumGroup g ON f.forumGroupID = g.forumGroupID
WHERE s.[LogDate] between #date1 AND #date2
group by s.topicid, t.subject, f.Forumname, t.Datum, g.[name]
order by count(*) desc
Btw, i LOVE this site. Amazing design and usability! Hope it works good to get some help to :)
Instead of joining all tables in group by you can join the given tables by yourself.
Can you try this;
from s in db.ForumStatsSet
join t in db.Topics on t.TopicId == s.TopicId
join f in db.Forums on f.ForumId == t.ForumId
join fg in db.ForumGroups on fg.ForumGroupId == f.ForumGroupId
where s.LogDate >= date1 && s.LogDate <=
group s by new { t.TopicId, t.subject, f.forumName, t.datum, fg.name, f.forumID } into g
orderby g.Count() descending
select new TopicStatsData
{
TopicId = g.Key.topicID,
Count = g.Count(),
Subject = g.Key.subject,
ForumGroupName = g.Key.name,
ForumName = g.Key.forumName,
ForumId = g.Key.forumID
});
ps: There may be some errors, but logically it should be correct!
I think the problem is with the grouping construct. Try extract the data first (so there is not need to go via properties), then group by the extracted data.
IOW, try write the LINQ like would for the SQL.