LINQ multiple left outer join with a "or" clause - c#

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

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 };

Lambda expression in place of a left join

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

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.

How to perform multiple joins in LINQ

I have a stored procedure that I'm converting to LINQ, but I'm a little stuck. Here's the SQL:
select
#name = tp.Name
, #Entity = tc.Entity
, #Name = tc.Name
, #ID = tpt.ID
, #Code = tptr.Code
from tbltprop tp
Left join tblcol tc on ( tc.id = tp.ID )
Left join tblPropday tpt on ( tpt.Id = tp.Id )
Left join tblProperResult tptr on (tptr.ID = tpt.Id )
where tp.id = #chsarpID1 //input ID i get in c#
and tpt.Id = #chsarpID2
I understand how to do a single join, but I'm having some trouble making a multiple join work. How would one go about this?
This might help you.......
var values= from tp in tbltprop
join tb in tblcol ON tp.id equals tb.id into gj
from gjd in gj.DefaultIfEmpty()
join tpt in tblPropday ON tp.id equals tpt.id into gk
from gkd in gk.DefaultIfEmpty()
join tptr in tblProperResult ON tp.id equals tptr.id into gl
from gld in gl.DefaultIfEmpty()
where tp.id == #charpID1 && gkd.Id == #CharpID2
select new
{
TpName = tp.Name,
Entity = gjd.Entity,
TPTName = gjd.Name,
ID = gkd.ID,
Code = gld.Code
};

linq sql find duplicates but take null and empty string as the same

I have a half a half a million records table, that i need to find the duplicates. So i use this code i created:
var dups2 = from m in mg_B
group m by new { m.Addr1, m.Addr2, m.City, m.State }
into g
where g.Count() > 1
select g;
The problem with this code is that it will not take as duplicates 2 records that have addr1 as an empty string "" and respectively NULL.
Basically when comparing a null and an empty value of the field, it sees them as different, but i need to to be seen as the same.
I know i could go through every single record and replace the null values with "" but i took the computer 1 minute to go through 4 000 records. and this will be done repeatedly when someone clicks a button.
I found out about this null empty string issue because i initially created a class with just some of the fields (the table has over 40 fields).
List<CombineClass> mg = (from m in db.MG_Backup
where m.IsArchived == false
select new CombineClass { id = m.ID, name = m.Name, addr1 = string.IsNullOrEmpty(m.Addr1) ? "" : m.Addr1, addr2 = string.IsNullOrEmpty(m.Addr2) ? "" : m.Addr2, city = m.City, state = m.State }).ToList();
Any ideas ?
This version is compatible with Linq-to-Sql / Linq-to-Entities
var dups2 = from m in mg_B
group m by new
{
Addr1 = m.Addr1 ?? string.Empty,
Addr2 = m.Addr2 ?? string.Empty,
City = m.City ?? string.Empty,
State = m.State ?? string.Empty,
}
into g
where g.Count() > 1
select g;
The generated sql looks a bit like this:
-- Parameters
DECLARE #p0 NVarChar(1000) = ''
DECLARE #p1 NVarChar(1000) = ''
DECLARE #p2 NVarChar(1000) = ''
DECLARE #p3 NVarChar(1000) = ''
DECLARE #p4 Int = 1
SELECT [t2].[value2] AS [Addr1], [t2].[value22] AS [Addr2], [t2].[value3] AS [City], [t2].[value3] AS [State]
FROM (
SELECT COUNT(*) AS [value], [t1].[value] AS [value2], [t1].[value2] AS [value22], [t1].[value3], [t1].[value4]
FROM (
SELECT COALESCE([t0].[Addr1],#p0) AS [value], COALESCE([t0].[Addr2],#p1) AS [value2], COALESCE([t0].[City],#p2) AS [value3], COALESCE([t0].[State],#p3) AS [value4]
FROM [SettingSystemNodes] AS [t0]
) AS [t1]
GROUP BY [t1].[value], [t1].[value2], [t1].[value3], [t1].[value4]
) AS [t2]
WHERE [t2].[value] > #p4
Note that you if you set string.Empty to a local variable before or even a let variable inside the query, only one parameter will be used for the empty string.
Well here's the brute force way:
var dups2 = from m in mg_B
group m by new {
Addr1 = (string.IsNullOrEmpty(m.Addr1) ? "" : m.Addr1),
Addr2 = (string.IsNullOrEmpty(m.Addr2) ? "" : m.Addr2),
City = (string.IsNullOrEmpty(m.City) ? "" : m.City ),
State = (string.IsNullOrEmpty(m.State) ? "" : m.State),
...
}
into g
where g.Count() > 1
select g;
If you want the code to look cleaner you could have an extension method on string:
public static string EmptyForNull(this string s)
{
return string.IsNullOrEmpty(s) ? "" : s;
}
and then your query would be:
var dups2 = from m in mg_B
group m by new {
Addr1 = EmptyForNull(m.Addr1),
Addr2 = EmptyForNull(m.Addr2),
City = EmptyForNull(m.City),
State = EmptyForNull(m.State),
...
}
into g
where g.Count() > 1
select g;
However, this would probably be a LOT faster if it were done in SQL rather than Linq.

Categories

Resources