Multiple table linq query? - c#

Could you help me translate this SQL query in Linq and Lambda expression.
SELECT * FROM User
JOIN UserIdentifier ON User.id = UserIdentifier.user_fk
JOIN UserPassword ON User.id = UserPassword.user_fk
WHERE UserIdentifier.identifier_value = "key" AND UserPassword.password = "1234"
I already wrote this
var query = from u in context.Users
join ui in context.UserIdentifiers on u.id equals ui.user_fk into Joined
from j in Joined.DefaultIfEmpty()
join up in context.UserPasswords on u.id equals up.user_fk into Joined2
from ???
select new { id = u.id, identifier = j.identifier_value, password = Joined2.???}
with help of http://paragy.wordpress.com/2010/11/18/multiple-joins-with-linq-and-lambda/
I'm not a happy user of linq. Actually I don't like linq because of this kind of request. This one is simple but when you try complex request linq is a nightmare. It's even worst with dynamic request. I always have problem with linq syntax and the web doesn't really help. I don't find a correct documentation to write queries.
I guess I'm not the first one to ask this question but all the documentation I found seems wrong or doesn't help me. This is a simple query and I don't find correct help. I'm still waiting someone prove me that link is not just another POC.

You're missing a where clause.
And your SQL joins are regular INNER JOINs, so you don't need these join ... into g from x in g.DefaultIfEmpty() -> that's how you do an equivalent of LEFT OUTER JOIN.
var query = from u in context.Users
join ui in context.UserIdentifiers on u.id equals ui.user_fk
join up in context.UserPasswords on u.id equals up.user_fk
where ui.identifier_value == "key" && up.password == "1234"
select new
{
id = u.id,
identifier = ui.identifier_value,
password = up.password
};

Related

how to access table from embedded where LINQ c#

Please note below is entirely made up for example sake. I have a similar query based on an sql code but couldn't translate it to LINQ to get correct value.
The sql basically looks like this:
select * from customers c
join proucts p on c.id = p.customerid
join credit r on r.customerid=c.id and ISNULL(r.trandate, c.registeredDate) >= c.registeredDate
I also tried to tweak the above sql and put the condition inside where and it also returns the same value I am getting in my #2 LINQ below(which is incorrect).
How can I use c (customer) inside .Where of credit? see code
1.
from c in customers
join p in products on c.id = p.customerid
join cr in credit.Where(r=> r.tranDate => c.registeredDate!=null?c.registeredDate : r.purchaseDate) on c.id=cr.customerid
...
2.
I know you would suggest why not just put it in a where below like below but I am getting incorrect value.
from c in customers
join p in products on c.id = p.customerid
join cr in credit on c.id=cr.customerid
where r.tranDate => c.registeredDate!=null?c.registeredDate : r.purchaseDate
Is there a workaround? I have tried tons of others but won't get me the correct one.
LINQ supports only equijoins. Any additional criteria should go to where clause. And yes, the other range variables are inaccessible from the join inner sequence, so the filtering should happen before or after the join.
So this SQL query:
select * from customers c
join products p on c.id = p.customerid
join credit r on r.customerid = c.id
and ISNULL(r.trandate, c.registeredDate) >= c.registeredDate
directly translates to this LINQ query:
from c in customers
join p in products on c.id equals p.customerid
join cr in credit on c.id equals cr.customerid
where (cr.tranDate ?? c.registeredDate) >= c.registeredDate
select new { c, p, cr };
Optionally, the condition
(cr.tranDate ?? c.registeredDate) >= c.registeredDate
can be replaced with
(cr.tranDate == null || cr.tranDate >= c.registeredDate)

Why do I have do assign subquery to a variable outside of my main query

In the GetTransfers() method below I have to assign the result of GetAllocations() to a variable outside of my main query otherwise the query fails. Why do I have to do that? Is there a better way?
When the query fails I get this error:
{System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[XCBusinessLogic.Presentation.Allocation] GetAllocations()' method, and this method cannot be translated into a store expression.
This query works:
public IQueryable<Transfer> GetTransfers()
{
IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
//let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = wxyz.Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
This query fails:
public IQueryable<Transfer> GetTransfers()
{
//IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = wxyz.Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
This query fails:
public IQueryable<Transfer> GetTransfers()
{
//IQueryable<Allocation> wxyz = GetAllocations();
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
//let wxyz = GetAllocations()
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = GetAllocations().Where(x => x.TRANSFER_ID == transfer.TRANSFER_ID)
};
return query;
}
GetAllocations Method:
public IQueryable<Allocation> GetAllocations()
{
IQueryable<Allocation> query =
from alloc in Context.XC_ALLOCATIONS
join acm in Context.ACMS on alloc.ACCT_NO equals acm.ACCT_NO
join b in Context.BUM_DETAILS.Where(x => x.FIRM_NO == 1 && x.CATEGORY_ID == 1937) on acm.ACCT_NO equals b.ACCT_NO into bumDetails
from bumDetail in bumDetails.DefaultIfEmpty()
where acm.FIRM_NO == 1
select new Allocation
{
AccountName = acm.ACCT_NAME
// snip
};
return query;
}
Linq to Entities translate everything in the query from transfer in Context.XC_TRANSFERS ... into SQL. So the only expressions that are allowed inside that query are ones that can easily be translated to SQL.
Linq to Entities cannot figure out how a .NET method like GetAllocations() works. How should it do that? There could be any form of crazy code inside a method. How could it turn that into SQL?
In your case the method actually contains another Linq to Entities query. Maybe you could copy-paste one query into the interior of the other. But I don't think that would improve your code!
So just keep the working solution you have.
You can get around the problem by using join with your method followed by an into
IQueryable<Transfer> query =
from transfer in Context.XC_TRANSFERS
join allocation in GetAllocations() on transfer.TRANSFER_ID equals allocation.TRANSFER_ID into allocationList
join trader in Context.MGRS on transfer.TRADER_ID equals trader.MGR_NO
join ssm in Context.SSM_CORES on transfer.SSM_ID equals ssm.SSM_ID
join desk in Context.XC_DESKS on transfer.DESK_ID equals desk.DESK_ID
select new Transfer
{
// snip
_AllocationList = allocationList
};
I was having a very similar issue and the answer from Aducci did it for me. This was what I was trying to do:
query = from x in query
where GetServicesQuery(db, options).Any(service => /*my criteria*/)
select x;
It was resolved by doing this as Aducci suggested:
query = from x in query
join service in GetServicesQuery(db, localOptions) on x.ID equals service.ID into services
where services.Any(service => /*my criteria*/)
select x;
I am posting this solution because my case was different from above (needing the subquery in the where not the select). If anyone ever stumbles onto this thread with the same issue as me, hopefully this will save them some searching around.
This one was stressing me out since GetServicesQuery has a lot of criteria that I don't want to keep repeating.

Sub Query in LINQ

I have an SQL Query as given below
SELECT ui.PageStyleCss
FROM UserImages ui
WHERE ui.UserImageId IN
( SELECT inv.UserImageId
FROM Invitation inv
JOIN InviteeEmails invEmails ON
inv.InviteID = invEmails.InviteID
WHERE invEmails.InviteGUID = #InviteGUID
)
How can I write this in LINQ?
Thanks
My wild guess is that you're using LINQ to SQL. It would be nice if you mentioned this, along with details of your model. Guessing at its structure...
var q = from ui in Context.UserImages
where ui.Invitations.Any(i => i.InviteeEmails.Any(e => e.InviteGuid = inviteGuid))
select ui.PageStyleCss;
from ui in db.UserImages
where (from inv in db.Invitations
join invEmails from InviteeEmails
on inv.InviteId equals invEmails.InviteId
where invEmails.InviteGUID == inviteGUID
select inv.UserImageId).Contains(ui.UserImageId)
select ui.PageStyleCss
(not sure if it compiles or not)
I have to assume there's a better way...this is pretty much a direct translation.

How to do a subquery in LINQ?

Here's an example of the query I'm trying to convert to LINQ:
SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
AND Users.Id IN (
SELECT UserId
FROM CompanyRolesToUsers
WHERE CompanyRoleId in (2,3,4) )
There is a FK relationship between CompanyRolesToUsers and Users, but it's a many to many relationship and CompanyRolesToUsers is the junction table.
We already have most of our site built, and we already have most of the filtering working by building Expressions using a PredicateExtensions class.
The code for the straightforward filters looks something like this:
if (!string.IsNullOrEmpty(TextBoxLastName.Text))
{
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
}
e.Result = context.Users.Where(predicateAnd);
I'm trying to add a predicate for a subselect in another table. (CompanyRolesToUsers)
What I'd like to be able to add is something that does this:
int[] selectedRoles = GetSelectedRoles();
if( selectedRoles.Length > 0 )
{
//somehow only select the userid from here ???:
var subquery = from u in CompanyRolesToUsers
where u.RoleID in selectedRoles
select u.UserId;
//somehow transform this into an Expression ???:
var subExpression = Expression.Invoke(subquery);
//and add it on to the existing expressions ???:
predicateAnd = predicateAnd.And(subExpression);
}
Is there any way to do this? It's frustrating because I can write the stored procedure easily, but I'm new to this LINQ thing and I have a deadline. I haven't been able to find an example that matches up, but I'm sure it's there somewhere.
Here's a subquery for you!
List<int> IdsToFind = new List<int>() {2, 3, 4};
db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
db.CompanyRolesToUsers
.Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
.Select(crtu => crtu.UserId)
.Contains(u.Id)
)
Regarding this portion of the question:
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
I strongly recommend extracting the string from the textbox before authoring the query.
string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));
You want to maintain good control over what gets sent to the database. In the original code, one possible reading is that an untrimmed string gets sent into the database for trimming - which is not good work for the database to be doing.
There is no subquery needed with this statement, which is better written as
select u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
or
select u.*
from Users u inner join CompanyRolesToUsers c
on u.Id = c.UserId --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
That being said, in LINQ it would be
from u in Users
from c in CompanyRolesToUsers
where u.Id == c.UserId &&
u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
or
from u in Users
join c in CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
Which again, are both respectable ways to represent this. I prefer the explicit "join" syntax in both cases myself, but there it is...
This is how I've been doing subqueries in LINQ, I think this should get what you want. You can replace the explicit CompanyRoleId == 2... with another subquery for the different roles you want or join it as well.
from u in Users
join c in (
from crt in CompanyRolesToUsers
where CompanyRoleId == 2
|| CompanyRoleId == 3
|| CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;
You could do something like this for your case - (syntax may be a bit off). Also look at this link
subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();
finalQuery = from u in Users where u.LastName.Contains('fra') && subQuery.Contains(u.Id) select u;
Ok, here's a basic join query that gets the correct records:
int[] selectedRolesArr = GetSelectedRoles();
if( selectedRolesArr != null && selectedRolesArr.Length > 0 )
{
//this join version requires the use of distinct to prevent muliple records
//being returned for users with more than one company role.
IQueryable retVal = (from u in context.Users
join c in context.CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains( "fra" ) &&
selectedRolesArr.Contains( c.CompanyRoleId )
select u).Distinct();
}
But here's the code that most easily integrates with the algorithm that we already had in place:
int[] selectedRolesArr = GetSelectedRoles();
if ( useAnd )
{
predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id));
}
else
{
predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id) );
}
which is thanks to a poster at the LINQtoSQL forum
Here's a version of the SQL that returns the correct records:
select distinct u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)
Also, note that (2,3,4) is a list selected from a checkbox list by the web app user, and I forgot to mention that I just hardcoded that for simplicity. Really it's an array of CompanyRoleId values, so it could be (1) or (2,5) or (1,2,3,4,6,7,99).
Also the other thing that I should specify more clearly, is that the PredicateExtensions are used to dynamically add predicate clauses to the Where for the query, depending on which form fields the web app user has filled in. So the tricky part for me is how to transform the working query into a LINQ Expression that I can attach to the dynamic list of expressions.
I'll give some of the sample LINQ queries a shot and see if I can integrate them with our code, and then get post my results. Thanks!
marcel

How do I most elegantly express left join with aggregate SQL as LINQ query

SQL:
SELECT
u.id,
u.name,
isnull(MAX(h.dateCol), '1900-01-01') dateColWithDefault
FROM universe u
LEFT JOIN history h
ON u.id=h.id
AND h.dateCol<GETDATE()-1
GROUP BY u.Id, u.name
A solution, albeit one that defers handling of the null value to the code, could be:
DateTime yesterday = DateTime.Now.Date.AddDays(-1);
var collection=
from u in db.Universe
select new
{
u.id,
u.name,
MaxDate =(DateTime?)
(
from h in db.History
where u.Id == h.Id
&& h.dateCol < yesterday
select h.dateCol
).Max()
};
This does not produce exactly the same SQL, but does provide the same logical result. Translating "complex" SQL queries to LINQ is not always straightforward.
var collection=
from u in db.Universe
select new
{
u.id,
u.name,
MaxDate =(DateTime?)
(
from h in db.History
where u.Id == h.Id
&& h.dateCol < yesterday
select h.dateCol
).Max()
};
Just youse the above code and this should work fine!
You're going to want to use the join into construct to create a group query.
TestContext db = new TestContext(CreateSparqlTripleStore());
var q = from a in db.Album
join t in db.Track on a.Name equals t.AlbumName into tracks
select new Album{Name = a.Name, Tracks = tracks};
foreach(var album in q){
Console.WriteLine(album.Name);
foreach (Track track in album.Tracks)
{
Console.WriteLine(track.Title);
}
}
This isn't a full answer for you, but on the left join piece you can use the DefaultIfEmpty operator like so:
var collection =
from u in db.Universe
join history in db.History on u.id = history.id into temp
from h in temp.DefaultIfEmpty()
where h.dateCol < DateTime.Now.Date.AddDays(-1)
select u.id, u.name, h.dateCol ?? '1900-01-01'
I haven't had the need to do any groupby commands yet, so I left that out as to not send you down the wrong path. Two other quick things to note. I have been unable to actually join on two parameters although as above there are ways to get around it. Also, the ?? operator works really well in place of the isnull in SQL.

Categories

Resources