Linq multiple join conditions using extra variables - c#

I have this query:
SELECT *
FROM transaction t
JOIN transactionDetail toTrans ON t.id = toTrans.tId and toTrans.FlowDirection= 1
JOIN transactionDetail fromTrans ON t.id = fromTrans.tId and fromTrans.FlowDirection= 0
Which I tried to recreate using anonymous types, as explained here.
byte toFlow = 1;
byte fromFlow = 1;
from trans in data.Transactions
join toTrans in data.TransactionDetails on new {trans.id, toFlow} equals new {toTrans.tId, toTrans.FlowDirection}
join fromTrans in data.TransactionDetails on new { trans.id, fromFlow } equals new { fromTrans.tId, fromTrans.FlowDirection }
Flowdirection is always either 1 or 0, so I'm using the toFlow byte. This however, gives the error:
The type of one of the expressions in the join clause is incorrect.
According to this answer, both name and types need to match. Which would mean:
byte FlowDirection= 1;
from trans in data.Transactions
join toTrans in data.TransactionDetails on new {trans.id, FlowDirection} equals new {toTrans.tId, toTrans.FlowDirection}
join fromTrans in data.TransactionDetails on new { trans.id, FlowDirection} equals new { fromTrans.tId, fromTrans.FlowDirection }
Which works! However, the second join needs to have a FlowDirection of value 0 instead of 1. How can I change the value of FlowDirection? I can't change the name of the variable or subtract 1 inside the anonymous object, or else this would have been easy.

Just to expand on the commentary:
Surely you can just use two constants (or literals)?, i.e.
from trans in data.Transactions
join toTrans in data.TransactionDetails
on new {ID = trans.id, Flow = (byte)1}
equals new {Id = toTrans.tId, Flow = toTrans.FlowDirection}
join fromTrans in data.TransactionDetails
on new { Id = trans.id, Flow = (byte)0}
equals new { Id = fromTrans.tId, Flow = fromTrans.FlowDirection }
Could FlowDirect - 1 not work because it turns FlowDirect into an int instead? Does subtracting an int from a byte turn the byte into an int maybe? Otherwise, I really don't know why your code works.
Yes, you would need to cast the result back to byte (or the literal 1 to byte so that byte operator "-" is used)

How can I change the value of FlowDirection? I can't change the name of the variable or subtract 1 inside the anonymous object
To change your variable inside the anonymous object simply do:
new { fromTrans.tId, FlowDirection = fromTrans.FlowDirection - 1 }
for example.

Another idea:
from trans in data.Transactions
join toTrans in data.TransactionDetails on trans.id equals new toTrans.tId
join fromTrans in data.TransactionDetails on trans.id equals fromTrans.tId
where toTrans.FlowDirection == 1 && fromTrans.FlowDirection == 1
I think this option should be easier to read.

Related

convert SQL to LINQ not working

I want to convert this SQL code to LINQ. Here is my SQL code:
SELECT Rooms.RoomName AS RoomNo, Beds.BedName AS Beds, Rooms.RoomType, ISNULL(CheckIn.CheckIntatus,'') AS Status
FROM CheckIn
INNER JOIN GuestBeds ON CheckIn.GuestBedId = GuestBeds.Id
AND (CheckIn.CheckInStatus = 1 OR CheckIn.CheckIntatus = 2 OR CheckIn.CheckSIntatus = 3)
RIGHT JOIN Beds ON GuestBeds.BedId = Beds.Id
INNER JOIN Rooms ON Beds.RoomId = Rooms.Id
LEFT JOIN Guests ON CheckIn.GuestId = Guests.Id
WHERE Beds.Active = 1 AND Rooms.Active = 1
ORDER BY RoomName, Beds
It works well which means it shows all the RoomName with CheckInStatus. If the Room is not presence in CheckIn table, ot will return the status as Null.
So I want to convert the code to LINQ. SO here is my LINQ code:
from b in Beds
join w in Rooms on b.RoomsId equals w.Id
where (a.CheckInStatus == 3 || a.CheckInStatus == 1 || a.CheckInStatus == 2)
join p in GuestBeds on b.Id equals p.BedId
join a in CheckIn on p.Id equals a.GuestBedId
join t in Guests on a.GuestId equals t.Id
where b.Active == true && w.Active == true
orderby w.RoomName
select new
{
RoomName = w.RoomName,
BedName = b.BedName,
Status = a.CheckInStatus
}
It didnt worked like the first code. It only show the data which contain CheckInStatus. I want it to show all the RoomName inside Room database
Normally I would post some rules for converting SQL to LINQ but this is complicated enough I think I'd need to make new rules. I commented out the references to Guests because as a LEFT JOIN it has no bearing on the answer.
Pull out the WHERE on individual tables and make them sub-queries:
var ActiveBeds = Beds.Where(b => b.Active == 1);
var ActiveRooms = Rooms.Where(r => r.Active == 1);
In LINQ, a RIGHT JOIN must be done by flipping the join to be a left join, so we will create the two sides as sub-queries.
Left side of RIGHT JOIN:
Translate the JOIN conditions that aren't part of an equi-join into a LINQ where clause on the appropriate tables (alternately this could be a subquery as above). The LEFT JOIN becomes a LINQ join/from ... DefaultIfEmpty() phrase, but as noted above isn't needed.
var CheckInsGuestBedsGuests = from c in CheckIn
where (c.CheckInStatus == 1 || c.CheckInStatus == 2 || c.CheckInStatus == 3)
join gb in GuestBeds on c.GuestBedId equals gb.Id
//join g in Guests on c.GuestId equals g.Id into gj
//from g in gj.DefaultIfEmpty()
select new { c, gb /*, g */ };
Right side of RIGHT JOIN:
The other side of the RIGHT JOIN includes an INNER JOIN so put them together in a sub-query:
var ActiveBedsRooms = from b in ActiveBeds
join r in ActiveRooms on b.RoomId equals r.Id
select new { b, r };
Finally, flip the sub-queries to create a left join using the same idiom as above:
var ans = from br in ActiveBedsRooms
join cgbg in CheckInsGuestBedsGuests on br.b.Id equals cgbg.gb.BedId into cgbgj
from cgbg in cgbgj.DefaultIfEmpty()
select new {
RoomNo = br.r.RoomName,
Beds = br.b.BedName,
br.r.RoomType,
Status = cgbg.c.CheckInStatus
};
NOTE: If you were not using LINQ to SQL, the Status expression would fail when cgbg is null and you would need
Status = cgbg?.c.CheckInStatus
but unfortunately LINQ to SQL/EF doesn't handle the null conditional operators yet.
BTW, nice query - brings back memories of when I used to write hotel front desk software :)

Convert SQL with multiple joins (with multiple conditions) of the same table to LINQ

How to convert an SQL to LINQ? Basically, I have a project_mstr table that has a PR_CLASS, PR_TYP and PR_GRP columns. Those 3 columns are values in the params_mstr under param_cd. For example, there's a record that has a PR_TYP value in param_cd with a Regular as its corresponding param_val value.
I installed Linquer but I'm not comfortable using it since I still have to create a connection to my database. I can't also find an online SQL to LINQ converter. So I'm asking the good guys here to please help me with the conversion.
SELECT
c.pr_id, c.pr_class, c.pr_typ, c.pr_grp, cp.pr_price,
c.gl_acct_id, c.pr_DESC "Project",
pm.param_val "Project Class", pm2.param_val "Project Type", pm3.param_val "Project Group"
FROM project_mstr c
JOIN
params_mstr pm ON c.pr_class = pm.param_id AND pm.param_cd = 'PR_CLASS'
JOIN
params_mstr pm2 ON c.pr_typ = pm2.param_id AND pm2.param_cd = 'PR_TYP'
JOIN
params_mstr pm3 ON c.pr_grp = pm3.param_id AND pm3.param_cd = 'PR_GRP'
JOIN
pr_price_mstr cp ON c.pr_id = cp.pr_id
JOIN
gl_acct_mstr gl ON c.gl_acct_id = gl.gl_acct_id
ORDER BY
c.crea_dt DESC;
LINQ-to-SQL only support equijoins, so if you need to introduce multiple values into the join, you can create an anonymous class to represent all of the values being joined on (note that the anonymous classes need to be the same type, which means that they need to have (1) exactly the same names of fields (2) of exactly the same type (3) in exactly the same order).
from c in ProjectMstr
join pm in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_CLASS" } equals new { pm.ParamId, pm.ParamCd }
join pm2 in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_TYP" } equals new { pm2.ParamId, pm2.ParamCd }
join pm3 in ParamsMstr on new { ParamId = c.ChClass, ParamCd = "CH_GRP" } equals new { pm3.ParamId, pm3.ParamCd }
// …
orderby c.CreaDt descending
select new {
c.ChId,
// …
ProjectClass = pm.ParamVal,
ProjectType = pm2.ParamVal,
ProjectGroup = pm3.ParamVal,
}
Alternatively, if it doesn't change the logic of the query, you can pull out the constant value from the join into a where.
from c in ProjectMstr
join pm in ParamsMstr on c.ChClass equals pm.ParamId
join pm2 in ParamsMstr on c.ChClass equals pm2.ParamId
join pm3 in ParamsMstr on c.ChClass equals pm3.ParamId
// …
where pm.ParamCd == "CH_CLASS"
where pm2.ParamCd == "CH_TYP"
where pm3.ParamCd == "CH_GRP"
orderby c.CreaDt descending
select new {
c.ChId,
// …
ProjectClass = pm.ParamVal,
ProjectType = pm2.ParamVal,
ProjectGroup = pm3.ParamVal,
}

How to perform a left join with an additional filtering in LINQ to entities?

I have several tables, the main one is called DefectRecord, others are called DefectArea, DefectLevel...etc and the one called DefectAttachment. And this problem is about joining DefectRecord with other tables to get a ViewModel for further use. What the hard part I am facing is about the DefectAttachment table.
DefectRecord has a 1-to-many relation with DefectAttachment. While there may be NO attachment at all for one defect record, there may be multiple attachments.
Logically I tried to perform a left join among DefectRecord & DefectAttachment, but there is one more requiredment:
If there is multiple attachments, select ONLY the oldest one(i.e. the
one with oldest CreatedDate field value)
I am stuck at this requirement, how can I perform this with LINQ-to-Entities? Below is the code of what I have now:
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join a in attachmentQuery on dr.DefectRecordID equals a.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
select new DefectRecordViewModel
{
DefectRecordCode = dr.Code,
DefectAttachmentContent = g == null ? null : g.FileContent,
LookupFilterName = ft.FilterName,
}).ToList();
The *Query variable are the IQueryable object which get the full list of corresponding table.
Group your results by the Code and FilterName and then for the content take that of the item in the group that has the oldest date
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join d in attachmentQuery on dr.DefectRecordID equals d.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
group g by new { dr.Code, ft.FilterName } into gg
select new DefectRecordViewModel
{
DefectRecordCode = gg.Key.Code,
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault() == null? null: gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault().FileContent,
LookupFilterName = gg.Key.FilterName,
}).ToList();
If using C# 6.0 or higher then you can do:
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime)
.FirstOrDefault()?.FileContent,

Comparing nullable and non-nullable properties on a join (Linq) [duplicate]

I am working on a WinForms project which requires to use Linq-To-Sql. I have been able to create my DataContext using the SqlMetal tool, and make some queries. But right now I have a problem that I havent been able to solve.
I am trying to make a LEFT OUTER JOIN as following:
MyDatabase db = new MyDatabase(...);
var query = from p in db.ParentTable
join t in db.ChildTable on new {A = p.child_ID, B = p.OtherID}
equals new {A = t.ID, B = t.OtherID} into j1
from c in j1.DefaultIfEmpty()
select new
{
...
};
When I write this query an error is raised at compile-time in the join word:
The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'
I know this error is caused by the comparison between p.child_ID and t.ID since p.child_ID is int? and t.ID is int. But, How can I solve this? How can I perform the LEFT OUTER JOIN without having this error??
p.child_ID is int? since this column is marked as IS NULL in SQL.
Hope someone can help me, thanks in advance.
You can use GetValueOrDefault() method. It retrieves the value of the current Nullable object, or the object's default value.
In terms of your example :
var query = from p in db.ParentTable
join t in db.ChildTable on new {A = p.child_ID.GetValueOrDefault(0), B = p.OtherID}
equals new {A = t.ID, B = t.OtherID} into j1
from c in j1.DefaultIfEmpty()
select new
{
...
};
If the p.child_ID become null than it will return 0. Hope this will help !!
The first problem was that the property names should be the same as said casperOne♦, but the second problem is that you're comparing a nullable-int, p.child_ID, with a non-nullable-int, t.ID. So you could use the null-coalescing operator in this way:
(int)(p.child_ID ?? default(int))
In this case returns p.child_ID if it isn't null else returns default(int), that is 0.
The query will be:
var query = from p in db.ParentTable
join t in db.ChildTable on new {A = (int)(p.child_ID ?? default(T)), B = p.OtherID}
equals new {A = t.ID, B = t.OtherID} into j1
from c in j1.DefaultIfEmpty()
select new
{
//...
};

error with linq join

I have this linq query:
var segreterie = from s in db.USR_Utenti join h in db.USR_Accounts
on new {s.ID, settings.GruppoSegreteria}
equals new {h.USR_UtentiReference,h.ID_Gruppo} select s;
that has this problem:
The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
how can i do to solve it?
The two types should be the same - which means they'll need the same property names as you're using anonymous types. Try this:
var segreterie = from s in db.USR_Utenti join h in db.USR_Accounts
on new {s.ID, Groups = settings.GruppoSegreteria}
equals new {ID = h.USR_UtentiReference, Groups = h.ID_Gruppo}
select s;
That's assuming that s.ID and h.USR_UtentiReference have the same type, and settings.GruppoSegreteria and h.ID_Gruppo do likewise.
On a similar note, if you are doing a join on a field which is nullable (for e.g., int and int?) you may have to do something like (where Field2 is int? in Table1 and int in Table2):
from l in Table1
join l2 in Table2
on new { l.Field1, Field2 = (l.Field2.Value == null ? -1 : l.Field2.Value) } equals new { l2.Field1, Field2 = l2.Field2 }

Categories