Convert SQL to LINQ, with multiple joins and a predicate - c#

I am struggling with trying to write a LINQ statement in LINQ or method syntax, based on the following SQL.
SELECT vr.*
FROM VisualReport vr
JOIN VisualReportRoleList vrl ON vr.VisualReportSK = vrl.VisualReportSK
JOIN Role r ON vrl.RoleSK = r.RoleID
JOIN UserRoleList url ON r.RoleID = url.RoleID
JOIN Users u ON url.UserID = url.UserID
WHERE u.ID = 1
I have tried many things, but without luck. I was going to post some of it here, but it just made this post really messy to read.
Can anyone here help me with this?

Join pattern works like this
Param 1. is the list to join on,
Param 2. key on the left (what your calling join on),
Param 3. key on the right (what your joining to),
Param 4. is the results selector how you want to select the results.
db.VisualReposts.Join(db.VisualRepostRoleLists,
vr => vr.VisualReportSK,
vrl => vrl.VisualReportSK
(vr, vrl) => new { vr, vrl}).Join(db.Roles,
vrj => vrj.vrl.RoleSK,
r => r.RoleID,
vrj, r => new {vr = vrj.vr,
vrl = vrj.vrl,
role = r} )
//follow the same patter for the rest of the joins.
// Also add your where clause, it might be a better idea to start
//from the users table so you can filter first
But if everything is properly foreign keyed, you don't need to do a join you can just access the ICollections. If you can or have them foreign keyed you can access them like this
var user = db.Users.Where(u => u.id == 1);
//everything should have an icollection now so you can access via
//user.UserRoleList.Roles ect or what ever you need

A simple one to one conversion. LinqPad should convert this to equivalent of your query.
var result = (from vr in VisualReport
join vrl in VisualReportRoleList on vr.VisualReportSK equals vrl.VisualReportSK
join r in Role on vrl.RoleSK equals r.RoleID
join url in UserRoleList on vrl.RoleSK equals url.RoleID
join u in Users on url.UserID equals u.UserID
where u.ID == 1
select vr).ToList();

Related

SQL to Linq Lambda

Does anybody know how to convert this on outerjoin on LINQ Lambda?
I wan to achieve this using lambda linq
SELECT * FROM Posts as A LEFT JOIN Reactions as B on A.Id = B.PostId AND #userId = b.userid
Here is my current linq code
return await _dbContext.Posts
.GroupJoin(_dbContext.Reactions,
post => post.Id, reaction => reaction.PostId,
(post, reactions) => new { post, reactions })
.SelectMany(x => x.reactions.DefaultIfEmpty(),
(post, reaction) => new { post.post, reaction })
What you want to accomplish can be done in two different ways in SQL, and those ways can be translated to Linq.
Depending on your scenario (volume of data, indexes, etc) you may want to need one or another
Option A: Join the filtered data
SELECT a.Name, b.*
FROM
tableA
LEFT JOIN tableB on
b.Action='delete' AND a.Id = b.Id
would be translated in LINQ to something similar to:
var query =
from a in db.TableA
join pet in db.TableB.Where(x => x.Action=="delete") on a equals b.TableA into gj
from leftJoined in gj.DefaultIfEmpty()
and using method syntax:
var query = tableA
.GroupJoin(
tableB.Where(x => x.Action == "delete"),
tableA => tableA,
tableB => tableB.tableA,
(tableA, tableBs) => new {tableA, tableBs}
).SelectMany(x => x.tableBs.DefaultIfEmpty())
Option B: Do the join and later filter the data
SELECT a.Name, b.*
FROM
tableA
LEFT JOIN tableB on a.Id = b.Id
WHERE
b.Id = NULL OR b.Action='delete'
would be translated to:
var query =
from a in db.TableA
join pet in db.TableB on a equals b.TableA into gj
from leftJoined in gj.DefaultIfEmpty()
where lefjoined == null || leftjoined.Action == "delete"
A left outer join is a join in which each element of the first collection is returned, regardless of whether it has any correlated elements in the second collection. You can use LINQ to perform a left outer join by calling the DefaultIfEmpty method on the results of a group join.
You can use this approach
Query Syntax:
var query = (from post in Posts
join reaction in Reactions
on post.Id equals reaction.PostId
into reaction
from reaction in reaction.DefaultIfEmpty()
select new
{
post.Id,
//prod.Foo1,
//post.Foo2,
//reaction.Foo3,
//reaction.Foo4,
//you can select other fields too
}).OrderBy(ps => ps.Id);
For more information visit Perform left outer joins
Normally you don't. Flattening out related data like that is simply not necessary in LINQ. Just fetch the data with its natural shape:
_dbContext.Posts.Include(p => p.Reactions)
This returns the Posts and any reactions, without having to repeat the Post data for each Reaction, or having nulls for Posts without Reactions.

How do I put a left join between two tables in Linq-to-SQL using the keys?

I wrote query in Linq to SQL. I need to put left join between db.secs and db.subs i.e. with db.subs being left table and the db.secs being right.
I wrote this but cannot figure out how to do that?
var qry = (from sr in db.secs
join s in db.subs
on sr.Id equals s.secId
join ss in db.subsSt
on s.Id equals ss.subId
join u in db.usersNew
on s.uid equals u.Id
where ss.isNew
group s by new { s.uid, u.UName, sr.Id, sr.Name } into totalGrp
select new
{
CreatorName = totalGrp.Key.UName,
SecName = totalGrp.Key.Name,
TotalRecs = totalGrp.Count()
}).OrderBy(o => o.CreatorName)
.ToList();
How do I make it re-arrange like first table?
In angular and HTML I am looping through collection and presenting in table.
You have to use DefaultIfEmpty() on the second table to generate the outer join or use navigation properties on the store. Something along the lines of
var query = from sr in db.secs
join s in db.subs into secSubs
from srs in secSubs.DefaultIfEmpty()
// rest of the query follows
Alternatively with navigation properties you might be able to do something like
var query = from sr in db.secs
from s in sr.Subs
select new {sr, s}
A third option is to possibly use a quasi ANSI-82 syntax rather than ANSI-92 join syntax and make the first item above more pallatable:
var query = from sr in db.secs
from s in db.Subs.Where(s1 => s1.subId == sr.Id).DefaultIfEmpty()
select new {sr, s}
I wrote up a more detailed post on this a while back at https://www.thinqlinq.com/Post.aspx/Title/Left-Outer-Joins-in-LINQ-with-Entity-Framework.

Is possible to get dictionary of values with linq?

I have solution in my mind, but I am convinced that there must be better solution for the problem.
Lets think I have two tables - User (n..n) Group. In my app I wish to get list of Users, each with all his Groups. In SQL I can achieve similar result with:
SELECT TOP (1000) u.Name, g.Name
FROM [User] u
join [GroupUser] gu on u.Id = gu.UserId
join [Group] g on gu.GroupId = g.Id
In the code I am able to write something like this (please, don't judge the correctness, it's just for illustration):
var result = new Dictionary<Model.User, List<Model.Group>>();
List<string> names = request.Names;
var query = this.dbContext.Set<Model.User>().AsQueryable();
query = from user in query
where names.Contains(user.Name, StringComparer.OrdinalIgnoreCase)
select user;
foreach(var user in await query.ToListAsync())
{
var groupQuery =
from g in this.dbContext.Set<Model.Group>()
join gu in this.dbContext.Set<Model.GroupUser>() on g.Id equals gu.GroupId
join u in this.dbContext.Set<Model.User>() on gu.UserId equals u.Id
where u.Name.Equals(user.Name, StringComparison.OrdinalIgnoreCase)
select g;
result.Add(user, await groupQuery.ToListAsync())
}
My question is -- is possible to achieve something like this with single linq query? Or do I really need to enumerate all the Users and fire new query for each of them? This code looks very resource demanding. It is quite simple, only one cycle, but it contains Users.Count+1 query evaluations.
Thanks in advance for hints.
Assuming you are using LINQ to SQL, you can combine the queries:
var requestedUsers = from user in query
where names.Contains(user.Name, StringComparer.OrdinalIgnoreCase)
select user;
var result = (from u in requestedUsers
join gu in this.dbContext.Set<Model.User>() on u.UserId equals gu.Id
join g in this.dbContext.Set<Model.Group>() on gu.UserId equals g.Id into gj
select new { u, gj })
.ToDictionary(ugj => ugj.u, ugj => ugj.gj.ToList());
If you have an entity like the following:
public class User
{
// other properties
public virtual List<Group> Groups { get; set; }
}
You can query both with an Include:
var users = dbContext.Users.Include(u => u.Groups)
.Where(u => names.Contains(u.Name, StringComparer.OrdinalIgnoreCase));

LINQ Left Join with multiple ON OR conditions

I'm sorry for telling that I've a little bit weak on LINQ, I always do write SQL query before I start working on the complicated LINQ.
I want to ask that how to convert this SQL Query into LINQ with LEFT JOIN with multiple ON conditons with the OR operator.,
m.MerchandiseId will be use for twice in ON condition
SELECT
*
FROM
Inbox AS i
INNER JOIN [User] AS u ON i.FromUserId = u.UserId
LEFT OUTER JOIN Merchandise AS m ON
u.MerchandiseId = m.MerchandiseId
OR
i.ToMerchantId = m.MerchandiseId
WHERE
i.ToCompanyId = 10
OR
i.FromCompanyId = 10
var message = (from i in db.Inbox
join u in db.User on i.FromUserId equals u.UserId
join m in db.Merchandise on u.MerchandiseId equals m.MerchandiseId //here I want to ON i.MerchantId = m.MerchandiseId, but it doesn't allow
where i.ToCompanyId == user.CompanyId || i.FromCompanyId == user.CompanyId
orderby i.CreatedAt descending
group m.MerchandiseId by new { m.MerchandiseId, m.MerchandiseName } into grp
select new
{
MerchandiseId = grp.Key.MerchandiseId,
MerchandiseName = grp.Key.MerchandiseName,
InboxMessage = (from e in db.Inbox
join us in db.User on e.FromUserId equals us.UserId
join mer in db.Merchandise on us.MerchandiseId equals mer.MerchandiseId
where mer.MerchandiseId == grp.Key.MerchandiseId
orderby e.CreatedAt descending
select e.InboxMessage).FirstOrDefault(),
CreatedAt = (from e in db.Inbox
join us in db.User on e.FromUserId equals us.UserId
join mer in db.Merchandise on us.MerchandiseId equals mer.MerchandiseId
where mer.MerchandiseId == grp.Key.MerchandiseId
orderby e.CreatedAt descending
select e.CreatedAt).FirstOrDefault(),
}).ToList();
The bottom LINQ Query I've write for it. However, I just can work on the left join with multiple ON clause in LINQ. Appreciate if someone would help me on this. Thanks!
I don't believe Linq supports the use of the OR operator with multiple columns, but that said, I wouldn't use OR even in SQL as it makes the join's intention unclear and it also obscures where the data originated from - it also isn't immediately clear what happens if there are multiple matches for each column. Instead I would JOIN twice on the different columns and let the projection-clause handle it:
SELECT
*
FROM
Inbox
INNER JOIN [User] AS u ON i.FromUserId = u.UserId
LEFT OUTER JOIN Merchandise AS userMerchant ON u.MerchandiseId = userMerchant.MerchandiseId
LEFT OUTER JOIN Merchandise AS inboxMerchant ON Inbox.ToMerchantId = inboxMerchant .MerchandizeId
WHERE
Inbox.ToCompanyId = 10
OR
Inbox.FromCompanyId = 10
This can then be translated into Linq using the LEFT OUTER JOIN approach ( How to implement left join in JOIN Extension method )
Note that if you're using Entity Framework then you don't need to worry about doing any of this at all! Just use Include:
var query = db.Inbox
.Include( i => i.User )
.Include( i => i.User.Merchandise )
.Include i => i.Merchandise )
.Where( i => i.ToCompanyId = 10 || i.FromCompanyId == 10 );

There has to be a better way to add this clause in linq

var result = from R in db.Clients.Where(clientWhere)
join RA in db.ClientAgencies on R.SysID equals RA.SysID
join A in db.Agencies.Where(agencyWhere) on RA.AgencyID equals A.AgencyID
join AC in db.AdCommittees on A.AgencyID equals AC.AgencyID into temp
from x in temp.DefaultIfEmpty().Distinct()
select new {R,RA,x};
If user enters CommitteeID this is what I do, but I feel there has to be a better way.
var query = (from R in result
where R.x.CommitteeID == params.CommitteeID
select R.R).Distinct();
return query;
Is there a better way?
How are you using the data. The joins could be hurting you depending on what you're trying to achieve (which is very difficult for us to view without context of your data structures).
I can't fault the linq other than to say that you appear to have a log of data being joined which you may or may not need.
The other problem I have is that you will execute the query when you call DefaultIfEmpty(). This means to do your filter you may hit the database again to calculate it's result.
Could you provide some info on your DB Schema and what you are trying to get from your query?
If you're not using your intermediate query for anything else, I would flip it (filter by committeeID first):
Client GetCommitteeClient(int committeeID)
{
return (
from AC in db.AdCommittees
where AC.CommitteeID == committeeID
join A in db.Agencies.Where(agencyWhere) on AC.AgencyID equals A.AgencyID
join RA in db.ClientAgencies on A.AgencyID equals RA.AgencyID
join R in db.Clients.Where(clientWhere) on RA.SysID equals R.SysID
select R
).SingleOrDefault();
}

Categories