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
Related
I have a complex query that I want to use either Query Builder or Eloquent (preferred) but I'm struggling with an inner join.
The inner join needs to be one of either of 2 conditions so if one fails, the other is used.
This is my original query
SELECT DISTINCT tableA.crmid, tableB.*
FROM tableB
INNER JOIN tableA ON tableA.crmid = tableB.customaccountsid
INNER JOIN tableC ON (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)
WHERE tableA.deleted = 0 AND tableC.relcrmid = 123 AND tableC.relation_id = 186
This is my attempt at using Query Builder and I know where the problem lies. It's where I join tableC. I don't know how to use my condition there
DB::table('tableB')
->join('tableA', 'tableA.crmid', '=', 'tableB.customaccountsid')
->join('tableC', function($join) {
$join->on(DB::raw('(tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)'));
})
->where('tableA.deleted', 0)
->where('tableC.relcrmid', 3727)
->where('tableC.relation_id', 186)
->select('tableA.crmid', 'tableB.*')
Ant this is the output of the query when i output as SQL
SELECT `tableA`.`crmid`, `tableB`.*
FROM `tableB`
INNER JOIN `tableA` ON `tableA`.`crmid` = `tableB`.`customaccountsid`
INNER JOIN `tableC` ON `tableC`.`relcrmid` = (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)
WHERE `tableA`.`deleted` = ? AND `tableC`.`relcrmid` = ? AND `tableC`.`relation_id` = ?
Just try this:
->join('tableC', function ($join){
$join->on(function($query){
$query->on('tableC.relcrmid', '=', 'tableA.crmid')
->orOn('tableC.crmid', '=', 'tableA.crmid');
});
})
It returns as in your original query:
INNER JOIN tableC ON (tableC.relcrmid = tableA.crmid OR tableC.crmid = tableA.crmid)
I have following problem with Linq to entities.
Diagram:
Linq to entities query (incomplete yet):
from a in Anlaesse
join b in Beurteilung on a.AnlassID equals b.AnlassID into ab
from b in ab.DefaultIfEmpty()
join r in Rangliste on a.AnlassID equals r.AnlassId
join p in Pony on r.PonyId equals p.PonyID into rp
from p in rp.DefaultIfEmpty()
where a.AnlassID == 67
select new
{
BeurteilungId = b.BeurteilungID == null ? 0 : b.BeurteilungID,
PonyID = p.PonyID == null ? 0 : p.PonyID,
Name = p.Name,
PonyName1 = r.PonyName1,
AnlassId = a.AnlassID == null ? 0 : a.AnlassID
}
Generated SQL:
SELECT
[Extent1].[AnlassID] AS [AnlassID],
CASE WHEN ([Extent2].[BeurteilungID] IS NULL) THEN 0 ELSE
[Extent2].[BeurteilungID] END AS [C1],
CASE WHEN ([Extent4].[PonyID] IS NULL) THEN 0 ELSE [Extent4].[PonyID] END
AS [C2],
[Extent4].[Name] AS [Name],
[Extent3].[PonyName1] AS [PonyName1]
FROM [sspv].[Anlaesse] AS [Extent1]
LEFT OUTER JOIN [sspv].[Beurteilung] AS [Extent2] ON [Extent1].[AnlassID]
= [Extent2].[AnlassID]
INNER JOIN [sspv].[Rangliste] AS [Extent3] ON [Extent1].[AnlassID] =
[Extent3].[AnlassId]
LEFT OUTER JOIN [sspv].[Pony] AS [Extent4] ON [Extent3].[PonyId] =
[Extent4].[PonyID]
WHERE 67 = [Extent1].[AnlassID]
Problem is that I'm unable to add the left outer join between Pony and Beurteilung because all tables are already "used" in my query. Without this the result is wrong because it Returns for each Pony all Beurteilung.
Let's assume you want to left join Pony to Beurteilung. And let's assume both have a PonyId property. A clause like this would act as an inner join:
where p.PonyId == b.PonyId
A clause like this would act as a left join.
where b == null || p.PonyId == b.PonyId
However, you are finding this hard because it really doesn't look like you are using Entity Framework properly. Have you mapped any navigation properties or collections? If you haven't then add them because this is sort of the point of EF. If you have then you shouldn't have any need to use inner or left joins; you just traverse the navigation properties.
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()
I have ran into a snag with my Linq-to-Sql.
I have a sql query that runs the way I want and usually I use Linqer to convert to Linq to see the general idea. But this time my SQL query seems to advanced for Linqer. :/
I think the problem is the INNER JOINS that are nested in the LEFT OUTER JOIN. Unfortunately I have never ran into this before and don't know how to solve it using Linq.
My SQL query looks like this:
SELECT c.[Company], c.[Name_First], c.[Name_Last], ort.[IDOriginatorRoleType],
ort.[RoleType] AS [OriginatorRoleType], o.[IDOriginator], o.[IDWork],
o.[IDContact], m.[IDMedia], m.[IDWork], m.[FileName], m.[FileNameOnDisk],
m.[DateAdded], w.[IDWork] AS [IDWork2], w.[ArticleNumber], w.[Title],
w.[FrontPageLow], w.[FrontPageLowOnDisk], w.[FrontPageHigh],
w.[FrontPageHighOnDisk]
FROM [dbo].[tblSubscriptionsWorks] AS sw
INNER JOIN [dbo].[tblWorks] AS w ON sw.[IDWork] = w.[IDWork]
LEFT OUTER JOIN [dbo].[tblMedias] AS m ON m.[IDWork] = w.[IDWork]
LEFT OUTER JOIN ([dbo].[tblOriginators] AS o
INNER JOIN [dbo].[tblOriginatorRoles] AS ors ON
o.[IDOriginatorRole] = ors.[IDOriginatorRole]
INNER JOIN [dbo].[tblOriginatorRoleTypes] AS ort ON
ors.[IDOriginatorRoleType] = ort.[IDOriginatorRoleType]
INNER JOIN [dbo].[tblContacts] AS c ON
o.[IDContact] = c.[IDContact]) ON
(o.[IDWork] = w.[IDWork]) AND (ort.[IDOriginatorRoleType] = 1)
WHERE sw.[IDWork_Subscription] = 9942
The left outer join is not a problem what I can see. You just have to divide the statement
LEFT OUTER JOIN ([dbo].[tblOriginators] AS o
INNER JOIN [dbo].[tblOriginatorRoles] AS ors ON
o.[IDOriginatorRole] = ors.[IDOriginatorRole]
INNER JOIN [dbo].[tblOriginatorRoleTypes] AS ort ON
ors.[IDOriginatorRoleType] = ort.[IDOriginatorRoleType]
INNER JOIN [dbo].[tblContacts] AS c ON
o.[IDContact] = c.[IDContact]) ON
(o.[IDWork] = w.[IDWork]) AND (ort.[IDOriginatorRoleType] = 1)
into another IQueryable list. In the example the variable db is the datacontext. Here is a suggestion to a solution:
//selects all the columns that is just in the select from the left join
var leftJoin=
(
from o in db.tblOriginators
join ors in db.tblOriginatorRoles
on o.IDOriginatorRole equals ors.IDOriginatorRole
join ort in db.tblOriginatorRoleTypes
on ors.IDOriginatorRoleType equals ort.IDOriginatorRoleType
join c in db.tblContacts
on o.IDContact equals c.IDContact
where ort.IDOriginatorRoleType==1
select new
{
o.IDWork,
c.Company,
c.Name_First,
c.Name_Last,
ort.IDOriginatorRoleType,
ort.RoleType,
o.IDOriginator,
o.IDContact
}
);
var output=(
from sw in db.tblSubscriptionsWorks
join w in db.tblWorks
on sw.IDWork equals w.IDWork
from m in db.tblMedias
.Where(x=>x.IDWork==w.IDWork).DefaultIfEmpty()
//Left join with the IQueryable list
from org in leftJoin
.Where(x =>x.IDWork==w.IDWork).DefaultIfEmpty()
where
sw.IDWork_Subscription == 9942
select new
{
org.Company,
org.Name_First,
org.Name_Last,
org.IDOriginatorRoleType,
OriginatorRoleType=org.RoleType,
org.IDOriginator,
org.IDWork,
m.IDMedia,
m.IDWork,
m.FileName,
m.FileNameOnDisk,
w.FrontPageLow,
w.FrontPageLowOnDisk,
w.FrontPageHigh,
w.FrontPageHighOnDisk
}
);
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);