SQL to LINQ with JOIN and SubQuery - linq

I have a query that I' struggling to convert to LINQ. I just can't get my head around the required nesting. Here's the query in SQL (just freehand typed):
SELECT V.* FROM V
INNER JOIN VE ON V.ID = VE.V_ID
WHERE VE.USER_ID != #USER_ID
AND V.MAX > (SELECT COUNT(ID) FROM VE
WHERE VE.V_ID = V.ID AND VE.STATUS = 'SELECTED')
The Closest I've come to is this:
var query = from vac in _database.Vacancies
join e in _database.VacancyEngagements
on vac.Id equals e.VacancyId into va
from v in va.DefaultIfEmpty()
where vac.MaxRecruiters > (from ve in _database.VacancyEngagements
where ve.VacancyId == v.Id && ve.Status == Enums.VacanyEngagementStatus.ENGAGED
select ve).Count()
...which correctly resolves the subquery from my SQL statement. But I want to further restrict the returned V rows to only those where the current user does not have a related VE row.

I've realised that the SQL in the question was misleading and whilst it led to technically correct answers, they weren't what I was after. That's my fault for not reviewing the SQL properly so I apologise to #Andy B and #Ivan Stoev for the misleading post. Here's the LINQ that solved the problem for me. As stated in the post I needed to show vacancy rows where no linked vacancyEngagement rows existed. The ! operator provides ability to specify this with a subquery.
var query = from vac in _database.Vacancies
where !_database.VacancyEngagements.Any(ve => (ve.VacancyId == vac.Id && ve.UserId == user.Id))
&& vac.MaxRecruiters > (from ve in _database.VacancyEngagements
where ve.VacancyId == vac.Id && ve.Status == Enums.VacanyEngagementStatus.ENGAGED
select ve).Count()

This should work:
var filterOutUser = <userId you want to filter out>;
var query = from vac in _database.Vacancies
join e in _database.VacancyEngagements
on vac.Id equals e.VacancyId
where (e.UserId != filterOutUser) && vac.MaxRecruiters > (from ve in _database.VacancyEngagements
where ve.VacancyId == vac.Id && ve.Status == Enums.VacanyEngagementStatus.ENGAGED
select ve).Count()
select vac;
I removed the join to VacancyEngagements but if you need columns from that table you can add it back in.

Related

Linq query - ON clause of inner join cannot compare two Guids

If the person you are searching is a CIA emplyee, take his CIAJobs.EmployerID, otherwise select People.ID
SELECT
case when CIAJobs.EmployeeID IS NULL then People.ID
else CIAJobs.EmployerID
end
FROM [FMO].[People] AS p
LEFT JOIN [FMO].[CIAJobs] j
ON (p.ID = j.[EmployeeID])
AND (j.[relationshipType] = '25a8d79d-377e-4108-8c92-0ef9a2e1ab63')
where p.ID = '1b66e032-94b2-e811-96e0-f48c508e38a2' // id of person you search for
OR
j.[EmployeeID] = '1b66e032-94b2-e811-96e0-f48c508e38a2' // id of person you search for
I tried doing this in Linq:
var a = from l in People
join x in CIAJobs
on l.Id equals x.EmployeeID && x.RelationshipTypeGuid equals Guid.Parse('25a8d79d-377e-4108-8c92-0ef9a2e1ab63')
into gcomplex
from xx in gcomplex.DefaultIfEmpty()
select (xx.EmployeeID == null) ? l.EmployeeId : x.EmployerID;
var b = a.ToList();
why does the query show an error because of this chunk: && x.RelationshipTypeGuid equals Guid.Parse('25a8d79d-377e-4108-8c92-0ef9a2e1ab63')
If I remove this part it shows no error.
Error is: operator && cannot be applied to operands of type Guid and Guid.
Can you help me correct the Linq query please logically and syntactically? Thank you.
You don't need join for multiple conditions in this scenario. Use this
var a = from l in People
join x in CIAJobs
.Where(z=>z.RelationshipTypeGuid
.Equals(Guid.Parse('25a8d79d-377e-4108-8c92-0ef9a2e1ab63')))
on l.Id equals x.EmployeeID
into gcomplex
from xx in gcomplex.DefaultIfEmpty()
select (xx.EmployeeID == null) ? l.EmployeeId : x.EmployerID;
var b = a.ToList();
But based on your problem statement this should do
var a = from l in People
join x in CIAJobs
on l.Id equals x.EmployeeID
into gcomplex
from xx in gcomplex.DefaultIfEmpty()
select (xx == null) ? l.EmployeeId : xx.EmployerID;
var b = a.ToList();

why does these queries generate different sql?

I have a problem with a linq query in Entity framework. I am querying on a field on a navigation property. The problem is that the generated sql is less than optimal. The example below is simplified, actually I am trying to pass an expression tree, and that is why the second query with the let binding is not a sufficient solution even though the produced sql is what I want.
So to summarize I have two questions:
Why is the generated sql different? And is there any way to produce a sql query which will not create a join for every criteria with expression trees?
Update: I realize that I had Include("Securities") on the first query and not the second when I first posted the question, but it does not change the way the criterias are applied, only which columns are selected.
var qry = db.Positions
.Where(criteria)
.ToList();
var qry1 = (from p in db.Positions
where p.Security.Country == "NO" || p.Security.Country == "US" || p.Security.Country == "GB"
select p).ToList();
var qry2 = (from p in db.Positions
let s = p.Security
where s.Country == "NO" || s.Country == "US" || s.Country == "GB"
select p).ToList();
--qry1
SELECT
[Extent1].* --All columns from tblPositions
FROM [dbo].[tblPositions] AS [Extent1]
LEFT OUTER JOIN [dbo].[tblSecurities] AS [Extent2] ON ([Extent2].[SecurityType] IN (1,2..)) AND ([Extent1].[Security] = [Extent2].[SecuritySeq])
LEFT OUTER JOIN [dbo].[tblSecurities] AS [Extent3] ON ([Extent3].[SecurityType] IN (1,2..)) AND ([Extent1].[Security] = [Extent3].[SecuritySeq])
LEFT OUTER JOIN [dbo].[tblSecurities] AS [Extent4] ON ([Extent4].[SecurityType] IN (1,2..)) AND ([Extent1].[Security] = [Extent4].[SecuritySeq])
LEFT OUTER JOIN [dbo].[tblSecurities] AS [Extent5] ON ([Extent5].[SecurityType] IN (1,2..)) AND ([Extent1].[Security] = [Extent5].[SecuritySeq])
WHERE [Extent2].[Country] = 'NO' OR [Extent3].[Country] = 'US' OR [Extent4].[Country] = 'GB'
--qry2
SELECT
[Extent1].*
FROM [dbo].[tblPositions] AS [Extent1]
LEFT OUTER JOIN [dbo].[tblSecurities] AS [Extent2] ON ([Extent2].[SecurityType] IN (1,2..)) AND ([Extent1].[Security] = [Extent2].[SecuritySeq])
WHERE [Extent2].[Country] IN ('NO','US','GB')
In your first query
var qry1 = (from p in db.Positions.Include("Security")
where p.Security.Country == "NO"
|| p.Security.Country == "US"
|| p.Security.Country == "GB"
select p).ToList();
It seems you assume that Entity Framework has magical powers and can deduce that each of the or statements in the generated expression tree are on the same object, and that it can combine them into a contains. It is, by far, not that smart.
Additionally, the rows returned by both queries are not the same. The second one would also need an include, to include the rows from security (if needed). Otherwise you could remove them from the first query (include only means return rows, it has nothing to do with the ability of the where clause to filter rows).
Or to make it more object oriented and readable.
var allowedCountries = new List<string>() { "NO, "US", "GB" };
var qry1 = (from p in db.Positions
// I'm not sure if this is exactly correct
where p.Security.Country in allowedCountries
select p).ToList();
or Lambda (I'm much more familiar with)
var qry1 = db.Positions
.Where(p => allowedCountries.Contains(p.Security.Country))
.ToList();

LINQ Statement Doesn't Compile

This statement won't compile:
query = from g in context.GridViews
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
where g.GridTypeID == id && ( g.IsShared == true || g.RepID == me.clsRep.OID)
&& f.RepID == me.clsRep.OID
select g;
The compiler error is this (and it's underlining the last part of the where clause:
The name 'f' does not exist in the current context
It's logical SQL counterpart would be:
declare #RepID int
declare #GridTypeID int
select #RepID=15, #GridTypeID=5
select g.*,f.*
from
GridViews g
left outer join GridViewFavorites f on f.GridViewID = g.ID
where
g.GridTypeID = #GridTypeID and (g.IsShared = 1 or g.RepID == #RepID)
and f.RepID == #RepID
NOTE: per #hdv 's good catch the SQL sample should actually be:
select g.*,f.*
from
GridView g
left outer join GridViewFavorite f on f.GridViewID = g.ID and f.RepID = #RepID
where
g.GridTypeID = #GridTypeID and (g.IsShared = 1 or g.RepID = #RepID)
It's the "into" part of your join - once you've joined "into" a group, the join variable (f in this case) is out of scope - you've got to use gf instead. Alternatively, given that you're not actually using gf in your query at all, maybe you should just get rid of the into gf part entirely so it's a normal join instead of a group join.
However, that won't give you a left outer join. If you want a left outer join, you might want:
query = from g in context.GridViews
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
from f2 in gf.DefaultIfEmpty()
where g.GridTypeID == id && (g.IsShared == true || g.RepID == me.clsRep.OID)
&& (f2 == null || f2.RepID == me.clsRep.OID)
select g;
The left-join pattern for LINQ goes like this:
join f in context.GridViewFavorites on g.ID equals f.GridViewID into gf
from f in gf.DefaultIfEmpty() //missing
More information is available on Stack Overflow and Google.
The where clause acts on the joined result, so the f variable is not in scope.

Combining two tables using linq

i have two linq - sql queries, and im wondering how to join them..
First Query
var ab = from a in Items_worker.getCEAItems()
where a.ProjectCode == lbl_projectCode.Text
select new
{
a.ID
};
Second Query
var j = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID;
First Query would return:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
Second Query would return:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
0000-0000-0000-0000-0000
1111-1111-1111-1111-1111
question is how can i possibly join the two table. Wherein the second query should return all of the records with the same ID found in the first query plus the id containing "0000-0000-0000-0000-0000" second query..
The result should be:
fasf-1212-1212-1212-1212
afaa-1414-1414-1414-1414
0000-0000-0000-0000-0000
You can use union to join the both queries, for example split your second query in two with conditions like :
var ab = from a in Items_worker.getCEAItems()
where a.ProjectCode == lbl_projectCode.Text
select new
{
a.ID
};
var j = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID where c.ID.Equals("0000-0000-0000-0000-0000");
var j1 = from c in tblInc_worker.get(c => c.MarginID == MarginID && c.IncTypeID == "CAPEX")
orderby c.DateCreated
select c.ID where !(c.ID.Equals("0000-0000-0000-0000-0000"));
var result = ab.Union(j.Union(j1));
Hope this helps..

many to many relationship

I am trying to write a linq to get data from many to many tables.
Here are the tables
Products (ID,Name,Description)
Products_Items (ID,ProductID,Description)
ProductsNeeds (ID,Name)
ProductsItems_Needs (ItemID,NeedsID)
This is the t-sql query
select gPro.Name,gProItems.ShortDescription,gProItems.Description,gNeeds.Name
from Products gPro
join Products_Items gProItems on gPro.ID = gProItems.ProductID
join ProductsItems_Needs gProNeeds on gProNeeds.ItemID = gProItems.ID
join ProductsNeeds gNeeds on gNeeds.ID = gProNeeds.NeedsID
where gProItems.ID = 1
this is the linq
var q = from p in objM.Products
join gpItems in objM.Products_Items on p.ID equals gpItems.ProductID
from needs in gpItems.ProductsNeeds
where gpItems.ID == 1
select p;
This query returns (Products) and it has the Produts_Items but it has not the ProductsNeeds.
What modifications should I do in order each Products_items to have the ProductsNeeds?
Thanks
Finally I found the solution.
The change was that instead of returning Products it returns Product_Items.
var q = from pItems in objM.Products_Items
join p in objM.Products on pItems.ID equals p.ID into joinedProducts
from p in joinedProducts.DefaultIfEmpty()
from needs in pItems.ProductsNeeds
where pItems.ID == 1
select pItems;

Resources