Using Linq to Entities and havign a NOT IN clause - c#

I have a SQL query that I am trying to convert to LINQ:
SELECT * FROM TABLE1
WHERE LICENSE_RTK NOT IN(
SELECT KEY_VALUE FROM TABLE2
WHERE REFERENCE_RTK = 'FOO')
So I wrote one query for inner query and then one query for the outer one and used Except:
var insideQuery = (from pkcr in this.Repository.Context.TABLE2 where pkcr.Reference_RTK == "FOO" select pkcr.Key_Value);
var outerQuery = (from pl in this.Repository.Context.TABLE1 select pl).Except(insideQuery);
But this is wrong. Cannot even compile it. What is the correct way of writing this?

You cannot compile second query, because Except should be used on Queryables of same type. But you are trying to apply it on Queryable<TABLE1> and Queryable<TypeOfTABLE2Key_Value>. Also I think you should use Contains here:
var keys = from pkcr in this.Repository.Context.TABLE2
where pkcr.Reference_RTK == "FOO"
select pkcr.Key_Value;
var query = from pl in this.Repository.Context.TABLE1
where !keys.Contains(pl.License_RTK)
select pl;
NOTE: Generated query will be NOT EXISTS instead of NOT IN, but that's what you want
SELECT * FROM FROM [dbo].[TABLE1] AS [Extent1]
WHERE NOT EXISTS
(SELECT 1 AS [C1]
FROM [dbo].[TABLE2] AS [Extent2]
WHERE ([Extent2].[Reference_RTK] == #p0) AND
([Extent2].[Key_Value] = [Extent1].[License_RTK]))

Related

LINQ - EXISTS, Reference Parent Field in Subquery

I'm working on creating a dynamic linq expression with an optional where clause containing "exists".
Almost everything about this can be dynamic. The user can choose to filter only root level nodes or filter both the root and all descendant nodes. I've mocked up a working example of one of the paths this query takes that is giving me issues.
Example of T-SQL:
SELECT 'Child exists', ge.id AS [run_id], aseq.id, aseq.name
FROM group_execution ge
JOIN automation_sequences aseq ON (aseq.id=ge.automation_sequence_id)
WHERE EXISTS
(SELECT ge2.patriarch_id FROM group_execution ge2
JOIN automation_sequences aseq2 ON (aseq2.id=ge2.automation_sequence_id)
WHERE aseq2.name LIKE '%test%'
AND ge2.patriarch_id = ge.patriarch_id
UNION ALL
SELECT ase.patriarch_id FROM automation_sequence_executions ase
JOIN automation_sequences aseq2 ON (aseq2.id=ase.automation_sequence_id)
WHERE aseq2.name LIKE '%test%'
AND ase.patriarch_id = ge.patriarch_id)
The problem I'm having is LINQ is giving me an execution plan that looks like this:
SELECT 'Child exists', ge.id AS [run_id], aseq.id, aseq.name
FROM group_execution ge
JOIN automation_sequences aseq ON (aseq.id=ge.automation_sequence_id)
WHERE EXISTS
(SELECT 1
FROM
(SELECT ase.patriarch_id FROM automation_sequence_executions ase
JOIN automation_sequences aseq2 ON (aseq2.id=ase.automation_sequence_id)
WHERE aseq2.name LIKE '%test%'
UNION ALL
SELECT ge2.patriarch_id FROM group_execution ge2
JOIN automation_sequences aseq2 ON (aseq2.id=ge2.automation_sequence_id)
WHERE aseq2.name LIKE '%test%'
) AS sub
WHERE sub.patriarch_id = ge.patriarch_id)
Notice how it's putting the "where" clause outside the union. This plan is killing the performance of my query. The first query runs in less than a second but the second takes up to 30 seconds.
I'm having a ton of trouble figuring out how to write the LINQ expression to mimic the first query I have above.
Below is my code so far:
//creates first query from group_execution table and does joins
IQueryable<Contract_SeqExecution> result = this.queryGroups(context);
...
//query for children:
IQueryable<ATSM_DAL.App_Data.automation_sequence_executions> tcChildrenRuns = null;
IQueryable<ATSM_DAL.App_Data.group_execution> groupChildrenRuns = null;
if (queryTestCase)
tcChildrenRuns = (from ase in context.automation_sequence_executions
where ase.parent_group_exec_id != null
select ase);
if (queryGroup)
groupChildrenRuns = (from ge in context.group_execution
where ge.parent_group_exec_id != null
select ge);
//Perform Union All on child results
decendantResults = this.UnionAllTCGroups(tcChildrenRuns, groupChildrenRuns);
//handles dynamic filter, example: aseq2.name LIKE '%test%'
decendantResults = decendantResults.Where(filterExpression);
...
//Perform the EXISTS statement in where clause. This is the problem code
Expression<Func<Contract_SeqExecution, bool>> childFilter = r => decendantResults.AsExpandable().Any(c => c.patriarchId == r.patriarchId);
childFilter = childFilter.Or(filterExpression);
result = result.AsExpandable().Where(childFilter);

Anyone have an idea how to express complex join logic within a LINQ query?

I've been at it all day. For the life of me, I cannot figure out how to translate either of the final two select statements found within the below code snippet:
declare #Person table
(
[Name] varchar(50),
[ABA] varchar(9)
)
declare #Entity table
(
[Name] varchar(50),
[Respondent] varchar(9),
[TierRespondent] varchar(9)
)
insert into #Person ([Name], [ABA])
select 'Steve', '000000001'
union
select 'Mary', '000000002'
union
select 'Carey', '000000003'
insert into #Entity ([Name], [Respondent], [TierRespondent])
select 'Steve', '000000001', '000000006'
union
select 'Mary', '000000004', '000000002'
union
select 'Carey', '000000005', '000000008'
select *
FROM #Entity e
LEFT JOIN #Person p
ON p.[ABA] = e.Respondent
or p.[ABA] = e.[TierRespondent]
select *
FROM #Entity e
LEFT JOIN #Person p
ON p.[ABA] in (e.Respondent ,e.[TierRespondent])
The thing that boggles my mind is the logic found within the ON clause of the join statements.
I'm not a SQL wiz, so I've even failed at trying to restructure these SELECT statements into a different form that gives me the same results, but is also easier to translate to LINQ.
Any ideas, anyone?
Thanks.
After some hours of pondering, I found the following sql to be an acceptable refactoring that can easily be translated to a linq query:
select *
from #entity e
left join #Person p
on 1 = 1
where p.[ABA] = e.Respondent
or p.[ABA] = e.[TierRespondent]
and p.ABA is not null
And here is the corresponding linq query:
from entity in entities
join p in persons on 1 equals 1 into groupJoin
from person in groupJoin.DefaultIfEmpty()
where person.ABA == entity.Respondent || person.ABA == entity.TierRespondent
&& person.ABA != null
select new
{
entity.Name,
entity.Respondent,
entity.TierRespondent,
person?.Name,
person?.ABA
}
The select statements of the original post contained logic within the join clause. This logic cannot be expressed within a linq join clasue. This refactoring moves the logic from the join, and into the where clause, which can easily be handled expressed in linq.

Linq Join with a Group By

Ok, I am trying to replicate the following SQL query into a Linq expression:
SELECT
I.EmployeeNumber,
E.TITLE,
E.FNAM,
E.LNAM
FROM
Incidents I INNER JOIN Employees E ON I.IncidentEmployee = E.EmployeeNumber
GROUP BY
I.EmployeeNumber,
E.TITLE,
E.FNAM,
E.LNAM
Simple enough (or at least I thought):
var query = (from e in contextDB.Employees
join i in contextDB.Incidents on i.IncidentEmployee = e.EmployeeNumber
group e by new { i.IncidentEmployee, e.TITLE, e.FNAM, e.LNAM } into allIncEmps
select new
{
IncEmpNum = allIncEmps.Key.IncidentEmployee
TITLE = allIncEmps.Key.TITLE,
USERFNAM = allIncEmps.Key.FNAM,
USERLNAM = allIncEmps.Key.LNAM
});
But I am not getting back the results I exprected, so I fire up SQL Profiler to see what is being sent down the pipe to SQL Server and this is what I see:
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM ( SELECT DISTINCT
[Extent2].[IncidentEmployee] AS [IncidentEmployee],
[Extent1].[TITLE] AS [TITLE],
[Extent1].[FNAM] AS [FNAM],
[Extent1].[LNAM] AS [LNAM]
FROM [dbo].[Employees] AS [Extent1]
INNER JOIN [dbo].[INCIDENTS] AS [Extent2] ON ([Extent1].[EmployeeNumber] = [Extent2].[IncidentEmployee]) OR (([Extent1].[EmployeeNumber] IS NULL) AND ([Extent2].[IncidentEmployee] IS NULL))
) AS [Distinct1]
) AS [GroupBy1]
As you can see from the SQL string that was sent toSQL Server none of the fields that I was expecting to be return are being included in the Select clause. What am I doing wrong?
UPDATE
It has been a very long day, I re-ran the code again and now this is the SQL that is being sent down the pipe:
SELECT
[Distinct1].[IncidentEmployee] AS [IncidentEmployee],
[Distinct1].[TITLE] AS [TITLE],
[Distinct1].[FNAM] AS [FNAM],
[Distinct1].[LNAM] AS [LNAM]
FROM ( SELECT DISTINCT
[Extent1].[OFFNUM] AS [OFFNUM],
[Extent1].[TITLE] AS [TITLE],
[Extent1].[FNAM] AS [FNAM],
[Extent1].[LNAM] AS [LNAM]
FROM [dbo].[Employees] AS [Extent1]
INNER JOIN [dbo].[INCIDENTS] AS [Extent2] ON ([Extent1].[EmployeeNumber] = [Extent2].[IncidentEmployee]) OR (([Extent1].[EmployeeNumber] IS NULL) AND ([Extent2].[IncidentEmployee] IS NULL))
) AS [Distinct1]
But I am still not seeing results when I try to loop through the record set
foreach (var emps in query)
{
}
Not sure why the query does not return what it should return, but it occurred to me that since you only query the group key and not any grouped results you've got nothing but a Distinct():
var query =
(from e in contextDB.Employees
join i in contextDB.Incidents on i.IncidentEmployee equals e.EmployeeNumber
select new
{
IncEmpNum = i.IncidentEmployee
TITLE = e.TITLE,
USERFNAM = e.FNAM,
USERLNAM = e.LNAM
}).Distinct();
But EF was smart enough to see this as well and created a DISTINCT query too.
You don't specify which result you expected and in what way the actual result was different, but I really can't see how the grouping can produce a different result than a Distinct.
But how did your code compile? As xeondev noticed: there should be an equals in stead of an = in a join statement. My compiler (:D) does not swallow it otherwise. The generated SQL join is strange too: it also matches records where both joined values are NULL. This makes me suspect that at least one of your keys (i.IncidentEmployee or e.EmployeeNumber) is nullable and you should either use i.IncidentEmployee.Value or e.EmployeeNumber.Value or both.

Linq To Sql Left outer join - filtering null results

I'd like to reproduce the following SQL into C# LinqToSql
SELECT TOP(10) Keywords.*
FROM Keywords
LEFT OUTER JOIN IgnoreWords
ON Keywords.WordID = IgnoreWords.ID
WHERE (DomainID = 16673)
AND (IgnoreWords.Name IS NULL)
ORDER BY [Score] DESC
The following C# Linq gives the right answer.
But I can't help think I'm missing something (a better way of doing it?)
var query = (from keyword in context.Keywords
join ignore in context.IgnoreWords
on keyword.WordID equals ignore.ID into ignored
from i in ignored.DefaultIfEmpty()
where i == null
where keyword.DomainID == ID
orderby keyword.Score descending
select keyword).Take(10);
the SQL produced looks something like this:
SELECT TOP (10)
[t0].[DomainID]
, [t0].[WordID]
, [t0].[Score]
, [t0].[Count]
FROM [dbo].[Keywords] AS [t0]
LEFT OUTER JOIN
( SELECT 1 AS [test]
, [t1].[ID]
FROM [dbo].[IgnoreWords] AS [t1]
) AS [t2]
ON [t0].[WordID] = [t2].[ID]
WHERE ([t0].[DomainID] = 16673)
AND ([t2].[test] IS NULL)
ORDER BY [t0].[Score] DESC
How can I get rid of this redundant inner selection?
It's only slightly more expensive but every bit helps!
I think you can do something like this to eliminate the left join and maybe get more efficiency:
var query = (from keyword in context.Keywords
where keyword.DomainID == ID
&& !(from i in context.IgnoreWords select i.ID).Contains(keyword.WordID)
orderby keyword.Score descending
select keyword)
.Take(10);

Left Outer Join and Exists in Linq To SQL C# .NET 3.5

I'm stuck on translating a left outer join from LINQToSQL that returns unique parent rows.
I have 2 tables (Project, Project_Notes, and it's a 1-many relationship linked by Project_ID). I am doing a keyword search on multiple columns on the 2 table and I only want to return the unique projects if a column in Project_Notes contains a keyword. I have this linqtoSQl sequence going but it seems to be returning multiple Project rows. Maybe do an Exist somehow in LINQ? Or maybe a groupby of some sort?
Here's the LINQToSQL:
query = from p in query
join n in notes on p.PROJECT_ID equals n.PROJECT_ID into projectnotes
from n in notes.DefaultIfEmpty()
where n.NOTES.Contains(cwForm.search1Form)
select p;
here's the SQL it produced from profiler
exec sp_executesql N'SELECT [t2].[Title], [t2].[State], [t2].[PROJECT_ID],
[t2].[PROVIDER_ID], [t2].[CATEGORY_ID], [t2].[City], [t2].[UploadedDate],
[t2].[SubmittedDate], [t2].[Project_Type]FROM ( SELECT ROW_NUMBER() OVER (ORDER BY
[t0].[UploadedDate]) AS [ROW_NUMBER], [t0].[Title], [t0].[State], [t0].[PROJECT_ID],
[t0].[PROVIDER_ID], [t0].[CATEGORY_ID], [t0].[City], [t0].[UploadedDate],
[t0].[SubmittedDate], [t0].[Project_Type] FROM [dbo].[PROJECTS] AS [t0] LEFT OUTER JOIN
[dbo].[PROJECT_NOTES] AS [t1] ON 1=1 WHERE ([t1].[NOTES] LIKE #p0) AND
([t0].SubmittedDate] >= #p1) AND ([t0].[SubmittedDate] < #p2) AND ([t0].[PROVIDER_ID] =
#p3) AND ([t0].[CATEGORY_ID] IS NULL)) AS [t2] WHERE [t2].[ROW_NUMBER] BETWEEN #p4 + 1
AND #p4 + #p5 ORDER BY [t2].[ROW_NUMBER]',N'#p0 varchar(9),#p1 datetime,#p2 datetime,#p3
int,#p4 int,#p5 int',#p0='%chicago%',#p1=''2000-09-02 00:00:00:000'',#p2=''2009-03-02
00:00:00:000'',#p3=1000,#p4=373620,#p5=20
This query returns all mutiples of the 1-many relationship in the results. I found how to do an Exists in LINQ from here. http://www.linq-to-sql.com/linq-to-sql/t-sql-to-linq-upgrade/linq-exists/
Here is the LINQToSQL using Exists:
query = from p in query
where (from n in notes
where n.NOTES.Contains(cwForm.search1Form)
select n.PROJECT_ID).Contains(p.PROJECT_ID)
select p;
The generated SQL statement:
exec sp_executesql N'SELECT COUNT(*) AS [value] FROM [dbo].[PROJECTS] AS [t0] WHERE
(EXISTS(SELECT NULL AS [EMPTY] FROM [dbo].[PROJECT_NOTES] AS [t1] WHERE
([t1].PROJECT_ID] = ([t0].[PROJECT_ID])) AND ([t1].[NOTES] LIKE #p0))) AND
([t0].[SubmittedDate] >= #p1) AND ([t0].[SubmittedDate] < #p2) AND ([t0].[PROVIDER_ID] =
#p3) AND ([t0].[CATEGORY_ID] IS NULL)',N'#p0 varchar(9),#p1 datetime,#p2 datetime,#p3
int',#p0='%chicago%',#p1=''2000-09-02 00:00:00:000'',#p2=''2009-03-02
00:00:00:000'',#p3=1000
I get a SQL timeout from the databind() from using Exists.
it seems to be returning multiple Project rows
Yes, that's how join works. If a project has 5 matching notes, it show up 5 times.
What if the problem is - "Join" is the wrong idiom!
You want to filter the projects to those whose notes contain certain text:
var query = db.Project
.Where(p => p.Notes.Any(n => n.NoteField.Contains(searchString)));
You are going to have to use the DefaultIfEmpty extension method. There are a few questions on SO already that show how to do this. Here is a good example:
How can I perform a nested Join, Add, and Group in LINQ?

Categories

Resources