Lambda expression in place of a left join - c#

I try to convert T-SQL to lambda expression but I met a problem. Data is not correct.
This is my query
SELECT A.*
FROM
(SELECT UserId, MIN(ID) AS ID
FROM FingerMachineUsers
GROUP BY UserId ) A
LEFT OUTER JOIN
FingerTimeSheets B ON A.ID = B.UserNo
AND B.DayOfCheck = '2018-08-02 00:00:00.000'
WHERE
B.UserNo IS NULL
This is my lambda expression
dbContext.FingerMachineUsers
.GroupBy(x => x.UserId)
.Select(g => new { ID = g.Min(p => p.ID), UserId=g.Select(p => p.UserId) })
.GroupJoin(dbContext.FingerTimeSheets.Where(x=>x.DayOfCheck==shortDate),x=>x.ID,y=>y.UserNo,(x,y)=> new { ID = x, UserNo = y })
.SelectMany(x=>x.UserNo.DefaultIfEmpty(),(x,y)=>new { x.ID,y.UserNo});
Data returned is not correct.

The linq query should be:
DateTime date = DateTime.Today;
var innerQuery = from x in db.FingerMachineUsers
group x.UserId by x.UserId into y
select new { UserId = y.Key, ID = y.Min() };
var query = from x in innerQuery
join y in db.FingerTimeSheets on x.ID equals y.UserNo into z
from y in z.Where(a => a.DayOfCheck == date).DefaultIfEmpty()
where y == null || y.UserNo == null
select x;
The query is more or less equivalent to:
SELECT
[GroupBy1].[K1] AS [UserId],
[GroupBy1].[A1] AS [C1]
FROM (SELECT
[Extent1].[UserId] AS [K1],
MIN([Extent1].[UserId]) AS [A1]
FROM [dbo].[FingerMachineUsers] AS [Extent1]
GROUP BY [Extent1].[UserId] ) AS [GroupBy1]
LEFT OUTER JOIN [dbo].[FingerTimeSheets] AS [Extent2] ON (([GroupBy1].[A1] = [Extent2].[UserNo]) OR (([GroupBy1].[A1] IS NULL) AND ([Extent2].[UserNo] IS NULL))) AND ([Extent2].[DayOfCheck] = #p__linq__0)
WHERE [Extent2].[TimeSheetId] IS NULL OR [Extent2].[UserNo] IS NULL
(where TimeSheetId is the primary key of FingerTimeSheets)
There are some open points about the nullability of FingerTimeSheets.UserNo and about the meaning of B.UserNo IS NULL. Is FingerTimeSheets.UserNo nullable? Then the query is correct as is. If FingerTimeSheets.UserNo isn't nullable then change the where to:
where y == null
Another small problem that needs fixing is if both FingerMachineUsers.ID is nullable and FingerTimeSheets.UserNo is nullable. Change the moddile from y in z.Where() to:
from y in z.Where(a => UserNo != null && a.DayOfCheck == date).DefaultIfEmpty()

Related

How to write Linq query from Sql query

Can anyone please guide me on how I can write the LINQ for below SQL query
DECLARE #now DATETIME = dbo.GetInstanceDate(NULL);
select *
FROM [dbo].[creatives] AS [t0]
INNER JOIN [dbo].[contracts] AS [t1] ON [t0].[campaign_id] = [t1].[campaign_id]
LEFT OUTER JOIN [dbo].[vouchers] AS [t2] ON ([t1].[contract_id] = ([t2].[contract_id])) AND t0.creative_id = t2.creative_id
AND ([t2].[contract_id] = 29980)
AND (NOT ([t2].[removed] = 1))
AND ([t2].[active] = 1)
AND ((NOT ([t2].[start_date] IS NOT NULL)) OR (([t2].[start_date]) <= #now)) AND ((NOT ([t2].[expiration_date] IS NOT NULL)) OR (([t2].[expiration_date]) > #now))
AND (NOT ([t2].[creative_id] IS NOT NULL))
where [t1].contract_id = 29980
If you are looking for how to convert LEFT JOIN with complex filter there is simple way:
var query =
from t in context.Table
from o in context.OtherTable
.Where(o => o.id == t.Id && (o.Some == t.Some || o.Another == t.Another))
.DefaultIfEmpty()
select new { t, o };

Join multiple columns from the same table using Linq

I would like to convert following sql query into Linq to SQL
select distinct r.CompanyLogo, j.JobName, j.JobId, ur.UserId, r.JobSeekerID
, ur.FirstName, j.JobType, j.JobCareerLevel, j.JobLocation
from UserInterest m
join job j on m.FunctionalId = j.FunctionId or m.Careerlevel = j.CarrerLevelId or m.SalId = j.SalaryRangeId
join UserRegistration ur on j.UserId = ur.UserId
join EmplrRegistration r on j.UserId = r.JobSeekerID
where m.Status = 1 and m.UserId = 1
going through this I have so far tried following which didn't work out
var list = (from m in entities.UserInterests
from j in entities.Jobs
where m.FunctionalId == j.FunctionId || m.SalId == j.SalaryRangeId || m.Careerlevel == j.CarrerLevelId
&& m.Status == true && m.UserId == 1
join ur in entities.UserRegistrations on m.UserId equals ur.UserId
join r in entities.EmplrRegistrations on m.UserId equals r.JobSeekerID
select new { r.CompanyLogo, j.JobName, j.JobId, ur.UserId, r.JobSeekerID
, ur.FirstName, j.JobType, j.JobCareerLevel, j.JobLocation }).Distinct().ToList();
Edit:
following query is being generated against Svyatoslav Danyliv answer which is returning 7 rows instead of 6
SELECT 1 AS [C1], [Extent4].[CompanyLogo] AS [CompanyLogo], [Extent2].[JobName] AS [JobName]
,[Extent2].[JobId] AS [JobId], [Extent3].[UserId] AS [UserId]
,[Extent4].[JobSeekerID] AS [JobSeekerID], [Extent3].[FirstName] AS [FirstName]
,[Extent2].[JobType] AS [JobType], [Extent2].[JobCareerLevel] AS [JobCareerLevel]
,[Extent2].[JobLocation] AS [JobLocation]
FROM [dbo].[UserInterest] AS [Extent1]
INNER JOIN [dbo].[Job] AS [Extent2] ON ([Extent1].[FunctionalId] = [Extent2].[FunctionId])
OR (([Extent1].[FunctionalId] IS NULL) AND ([Extent2].[FunctionId] IS NULL))
OR ([Extent1].[Careerlevel] = [Extent2].[CarrerLevelId])
OR (([Extent1].[Careerlevel] IS NULL) AND ([Extent2].[CarrerLevelId] IS NULL))
OR ([Extent1].[SalId] = [Extent2].[SalaryRangeId])
OR (([Extent1].[SalId] IS NULL)
AND ([Extent2].[SalaryRangeId] IS NULL))
INNER JOIN [dbo].[UserRegistration] AS [Extent3] ON [Extent2].[UserId] = [Extent3].[UserId]
INNER JOIN [dbo].[EmplrRegistration] AS [Extent4] ON [Extent2].[UserId] = [Extent4].[JobSeekerID]
WHERE (1 = [Extent1].[Status]) AND (1 = [Extent1].[UserId])
Join which contains not just AND expressions is possible via from x in entities.Where(x => ..). You have did that partially and made mistake in where condition.
Corrected query, looks the same as original SQL
var query =
from m in entities.UserInterests
from j in entities.Jobs.Where(j =>
m.FunctionalId != null && m.FunctionalId == j.FunctionId ||
m.Careerlevel != null && m.Careerlevel == j.CarrerLevelId ||
m.SalId != null && m.SalId == j.SalaryRangeId)
join ur in entities.UserRegistrations on j.UserId equals ur.UserId
join r in entities.EmplrRegistrations on j.UserId equals r.JobSeekerID
where m.Status == true && m.UserId == 1
select new { r.CompanyLogo, j.JobName, j.JobId, ur.UserId, r.JobSeekerID
, ur.FirstName, j.JobType, j.JobCareerLevel, j.JobLocation };
var list = query.Distinct().ToList();

EntityFramework = LINQ

I've got some legacy code where there is a lot of raw SQL statements which I try to port to LINQ.
One of them looks like this
select s.Id, s.Name, s.Enabled, s.Tags, s.Description, s.ImageUrl, s.ImageFullUrl, sss.Id as StoneSupplierStoneId
from Stone s, StoneSupplierStone sss
where s.Id = sss.Stone_id and s.Enabled = 1
and sss.Id IN (select MAX(sss.Id)
from Stone s, StoneSupplierStone sss
where sss.StoneSupplier_id = 6142
and s.Id = sss.Stone_id and s.Enabled = 1
group by s.Name, (case when sss.CustomStoneName is null then s.Name else sss.CustomStoneName end))
order by s.Name
which I managed to port to LINQ using something like this
var sssq = from s in Stone
from sss in StoneSupplierStone
where sss.StoneSupplier_id == 6142
&& s.Id == sss.Stone_id
&& s.Enabled == true
let res = new { s.Name, sssName = sss.CustomStoneName == null ? s.Name : sss.CustomStoneName, sss.Id }
group res by new { res.Name, res.sssName } into g
select new { sssId = g.Max(_ => _.Id) };
var q = from s in Stone
from sss in StoneSupplierStone
where s.Id == sss.Stone_id
&& s.Enabled == true
&& sssq.Any(_ => _.sssId == sss.Id)
orderby s.Name
select new { s.Id, s.Name, s.Enabled, s.Tags, s.Description, s.ImageUrl, s.ImageFullUrl, StoneSupplierStoneId = sss.Id };
while I do get the same results, the underlying generated query looks pretty weird
-- Region Parameters
DECLARE #p0 Int = 1
DECLARE #p1 Int = 6142
DECLARE #p2 Int = 1
-- EndRegion
SELECT [t0].[Id], [t0].[Name], [t0].[Enabled], [t0].[Tags], [t0].[Description], [t0].[ImageUrl], [t0].[ImageFullUrl], [t1].[Id] AS [StoneSupplierStoneId]
FROM [Stone] AS [t0], [StoneSupplierStone] AS [t1]
WHERE (([t0].[Id]) = [t1].[Stone_id]) AND ([t0].[Enabled] = #p0) AND (EXISTS(
SELECT NULL AS [EMPTY]
FROM (
SELECT MAX([t4].[Id2]) AS [value]
FROM (
SELECT [t2].[Id], [t2].[Name], [t2].[Enabled], [t3].[Id] AS [Id2], [t3].[StoneSupplier_id], [t3].[Stone_id],
(CASE
WHEN [t3].[CustomStoneName] IS NULL THEN [t2].[Name]
ELSE CONVERT(NVarChar(256),[t3].[CustomStoneName])
END) AS [value]
FROM [Stone] AS [t2], [StoneSupplierStone] AS [t3]
) AS [t4]
WHERE ([t4].[StoneSupplier_id] = #p1) AND (([t4].[Id]) = [t4].[Stone_id]) AND ([t4].[Enabled] = #p2)
GROUP BY [t4].[Name], [t4].[value]
) AS [t5]
WHERE [t5].[value] = [t1].[Id]
))
ORDER BY [t0].[Name]
is there a way to rewrite my LINQ query to make it look more like the original SQL query?
P.S. I use LinqPad to test LINQ and generated query.

LINQ JOIN and OUTER JOIN - How to turn SQL into LINQ expression

I would like to turn the following SQL query into a LINQ expression (using Entity Framework 6.1). Thus far I have been unable find an acceptable LINQ expression that produces similar results. Any help turning this simple SQL statement into a LINQ express would be appreciated.
SELECT AAG.Id AS GroupId,
A.Id AS ActivityId,
A.Title As Title,
CASE WHEN AA.CompletedOn IS NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END AS Completed,
COALESCE(AAG.PointValue, 0) + SUM(COALESCE(AQ.PointValue, 0)) AS PointTotal
FROM ActivityAssignmentGroup AAG
INNER JOIN ActivityAssignment AA ON AA.GroupId = AAG.Id
INNER JOIN Activity A ON AA.ActivityId = A.Id
LEFT OUTER JOIN ActivityQuestion AQ ON AQ.ActivityId = A.Id
WHERE AAG.AssignedToId = 6
GROUP BY AAG.Id, A.Id, A.Title, CASE WHEN AA.CompletedOn IS NULL THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END, COALESCE(AAG.PointValue,0)
Without the LEFT OUTER JOIN portion, the below LINQ statement is partially complete, but I cannot figure out the appropriate syntax to add the LEFT OUTER JOIN condition:
var assignments = await (from g in db.AssignmentGroups.AsNoTracking().Where(x => x.AssignedToId == studentTask.Result.PersonId)
join aa in db.ActivityAssignments.AsNoTracking() on g.Id equals aa.GroupId
join a in db.Activities.AsNoTracking() on aa.ActivityId equals a.Id
select new ActivityListViewModel
{
Id = a.Id,
Points = g.PointValue ?? 0,
Title = a.Title,
GroupId = g.Id,
Complete = (aa.CompletedOn != null)
});
Edit:
Thanks for the response Bob. I attempted to use the DefaultIfEmpty and looked at the resultant SQL query generated by the Entity Framework, but it didn't work. Prior to making this post, this is the LINQ statement I attempted:
var assignments = from g in db.AssignmentGroups.AsNoTracking().Where(x => x.AssignedToId == studentTask.Result.PersonId)
join aa in db.ActivityAssignments.AsNoTracking() on g.Id equals aa.GroupId
join a in db.Activities.AsNoTracking() on aa.ActivityId equals a.Id
from aq in db.ActivityQuestions.Where(q => q.ActivityId == a.Id).DefaultIfEmpty()
group aq by new { ActivityId = aq.ActivityId, Title = a.Title, GroupId = g.Id, Points = g.PointValue ?? 0, Completed = (aa.CompletedOn != null) } into s
select new ActivityListViewModel
{
Id = s.Key.ActivityId,
Points = s.Key.Points + s.Sum(y => y.PointValue ?? 0), //g.PointValue ?? 0,
Title = s.Key.Title,
GroupId = s.Key.GroupId,
Complete = s.Key.Completed
};
Of course, it didn't work either. The result was items missing the Id (ActivityId).
You need DefaultIfEmpty() to convert a join to left outer join, documentition from MSDN here
var assignments = await (from g in db.AssignmentGroups.AsNoTracking().Where(x => x.AssignedToId == studentTask.Result.PersonId)
join aa in db.ActivityAssignments.AsNoTracking() on g.Id equals aa.GroupId
join a1 in db.Activities.AsNoTracking() on aa.ActivityId equals a1.Id into a2
from a in a2.DefaultIfEmpty()
select new ActivityListViewModel
{
Id = a == null ? null : a.Id,
Points = g.PointValue ?? 0,
Title = a == null ? null : a.Title,
GroupId = g.Id,
Complete = (aa.CompletedOn != null)
});
Just to close the loop (and thank you Bob Vale)... the query below works:
var assignments = from g in db.AssignmentGroups.AsNoTracking().Where(x => x.AssignedToId == studentTask.Result.PersonId)
join aa in db.ActivityAssignments.AsNoTracking() on g.Id equals aa.GroupId
join a in db.Activities.AsNoTracking() on aa.ActivityId equals a.Id
from aq in db.ActivityQuestions.Where(q => q.ActivityId == a.Id).DefaultIfEmpty()
group aq by new { ActivityId = a.Id, Title = a.Title, GroupId = g.Id, Points = g.PointValue ?? 0, Completed = (aa.CompletedOn != null) } into s
select new ActivityListViewModel
{
Id = s.Key.ActivityId,
Points = s.Key.Points + s.Sum(y => y.PointValue ?? 0), //g.PointValue ?? 0,
Title = s.Key.Title,
GroupId = s.Key.GroupId,
Complete = s.Key.Completed
};
The issue was the group by condition and using ag.ActivityId when I should have used a.Id.

LINQ multiple left outer join with a "or" clause

So basically, I try here to transfer this query in LINQ.
DECLARE #p1 UniqueIdentifier SET #p1 = 'AC1D85C1-28F1-46A3-9C6A-3B7446609A2A'
DECLARE #p2 UniqueIdentifier SET #p2 = NEWID()
SELECT
[MTD].[Description],
[MTD].[MessageTypeID],
ISNULL([AMT].[ApplicationMessageTypeID], NEWID()),
ISNULL([AMT].[EventForwardingRuleID], '1001')
FROM [dbo].[MessageType] as [MT]
INNER JOIN [dbo].[MessageTypeDescription] AS [MTD]
ON [MT].[MessageTypeID] = [MTD].[MessageTypeID]
LEFT OUTER JOIN [dbo].[ApplicationMessageType] AS [AMT]
ON [AMT].[MessageTypeID] = [MT].[MessageTypeID]
AND ( [AMT].[ApplicationID] = #p1 OR [AMT].[ApplicationID] IS NULL )
WHERE [MTD].[Culture] = 'fr'
I know for the most part that the query should look like something like that:
(from mt in db.MessageTypes
join mtd in db.MessageTypeDescriptions
on mt.MessageTypeID equals mtd.MessageTypeID
join amt in db.ApplicationMessageTypes
on new { mt.MessageTypeID, (applicationId || null) } equals new { amt.MessageTypeID, amt.ApplicationID }
into appMessageTypes
from amt in appMessageTypes.DefaultIfEmpty()
where mtd.Culture == culture
select new ApplicationEditEventTypeModel
{
ApplicationMessageTypeID = amt.ApplicationMessageTypeID == null ? Guid.NewGuid() : amt.ApplicationMessageTypeID,
Description = mtd.Description,
MessageTypeID = mtd.MessageTypeID,
EventForwardingRuleID = amt.EventForwardingRuleID == null ? 0 : amt.EventForwardingRuleID
});
The part here where I'm really not sure is the "ApplicationMessageTypes" part. For a multiple left join query I'd use the new {} equals new {} construct but in this case, I have 2 clauses ( [AMT].[ApplicationID] = #p1 OR [AMT].[ApplicationID] IS NULL ).
Should I use something like new { mt.MessageTypeID, new { applicationId ,null }} equals new { amt.MessageTypeID, amt.ApplicationID }? This seems too strange to be real.
(from mt in db.MessageTypes
join mtd in db.MessageTypeDescriptions
on mt.MessageTypeID equals mtd.MessageTypeID
from amt in db.ApplicationMessageTypes
.Where(a => a.MessageTypeID == mt.MessageTypeID &&
(a.ApplicationID == applicationId || !a.ApplicationID.HasValue)).DefaultIfEmpty()
where mtd.Culture == culture
select new ApplicationEditEventTypeModel
{
ApplicationMessageTypeID = amt.ApplicationMessageTypeID ?? Guid.NewGuid(),
Description = mtd.Description,
MessageTypeID = mtd.MessageTypeID,
EventForwardingRuleID = amt.EventForwardingRuleID ?? 0
});
I think the ApplicationId clause doesn't really look like its part of the JOIN - i.e. it's not really part of the foreign key relationship - instead it's really just a normal WHERE condition.
So I'd recommend moving the ApplicationId out to WHERE in both the SQL and in the LINQ
DECLARE #p1 UniqueIdentifier SET #p1 = 'AC1D85C1-28F1-46A3-9C6A-3B7446609A2A'
DECLARE #p2 UniqueIdentifier SET #p2 = NEWID()
SELECT
[MTD].[Description],
[MTD].[MessageTypeID],
ISNULL([AMT].[ApplicationMessageTypeID], NEWID()),
ISNULL([AMT].[EventForwardingRuleID], '1001')
FROM [dbo].[MessageType] as [MT]
INNER JOIN [dbo].[MessageTypeDescription] AS [MTD]
ON [MT].[MessageTypeID] = [MTD].[MessageTypeID]
LEFT OUTER JOIN [dbo].[ApplicationMessageType] AS [AMT]
ON [AMT].[MessageTypeID] = [MT].[MessageTypeID]
WHERE [MTD].[Culture] = 'fr'
AND (AMT IS NULL OR ([AMT].[ApplicationID] = #p1 OR [AMT].[ApplicationID] IS NULL ))
and
(from mt in db.MessageTypes
join mtd in db.MessageTypeDescriptions
on mt.MessageTypeID equals mtd.MessageTypeID
join amt in db.ApplicationMessageTypes
on new mt.MessageTypeID equals amt.MessageTypeID
into appMessageTypes
from amt in appMessageTypes.DefaultIfEmpty()
where mtd.Culture == culture
&& amt==null || (amt.ApplicationID == null || amt.ApplicationID == applicationId)
select new ApplicationEditEventTypeModel
{
ApplicationMessageTypeID = amt.ApplicationMessageTypeID == null ? Guid.NewGuid() : amt.ApplicationMessageTypeID,
Description = mtd.Description,
MessageTypeID = mtd.MessageTypeID,
EventForwardingRuleID = amt.EventForwardingRuleID == null ? 0 : amt.EventForwardingRuleID
});
You could probably also use a nested select (or a view) if you wanted to keep the ApplicationId clause closer to the original ApplicationMessageType table

Categories

Resources