In this pseudo-code, the last line won't work because the scalar value isn't IQueryable.
var scalarValue = 7;
var qry = (from t in db.Table
where t.Filter == 5 //Arbitrary
select t.ID);
var unioned = qry.Union(scalarValue);
It's straightforward in SQL:
select t.ID
from Table
union
select 7;
In my scenario I do need to select a valid Table.ID, so the workaround is:
var scalarValue = 7;
var qry = (from t in db.Table
where t.Filter == 5 //Arbitrary
select t.ID);
var scalarQry = (from t in db.Table
where t.ID == scalarValue
select t.ID);
var unioned = qry.Union(scalarQry);
However, if there is a way to output "select 7;" for example, then that's what I'm after for this question.
Writing
MyContext.MyClass.Select(t => t.Id).Union(new List<long>() { 42 }).ToList();
produces the following SQL query (MyClass is a DbSet<MyClass>) :
SELECT
[Distinct1].[C1] AS [C1]
FROM ( SELECT DISTINCT
[UnionAll1].[MyClassId] AS [C1]
FROM (SELECT
[Extent1].[MyClassId] AS [MyClassId]
FROM [dbo].[MyClasss] AS [Extent1]
UNION ALL
SELECT
cast(42 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]) AS [UnionAll1]
) AS [Distinct1]
The overload of Union which gets called is :
public static IQueryable<TSource> Union<TSource>(this IQueryable<TSource> source1, IEnumerable<TSource> source2)
in System.Linq.Queryable
I hope this will match your needs
Related
Consider the following data model:
A Principal has a number of Roles (many-to-many);
Roles grant multiple Permissions (many-to-many);
Now i want to use LINQ to determine whether a Principle has a permission, i.e. whether he is in any Roles that possess this permission.
Usually I would go for AnyAsync on the join table for cases like this , but since I am traversing more than one join table I am really struggling. I initially came up with this:
var query = context.Principals
.Where(p => p.Id == "SomeUUID")
.Include(p => p.Roles)
.ThenInclude(r => r.Permissions
.Where(p => p.Name == "SomePermission"));
But now I would have to traverse, probably in-memory, the attached Roles through the Principal again to search for Any Permission.
Is there a way I can smoothly apply this check within the same query?
EDIT: Here is the generated SQL to complement Parks'(bottom) answer in comparison:
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
INNER JOIN (
SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
INNER JOIN (
SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
) AS [t0] ON [t].[Id] = [t0].[RoleId]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND EXISTS (
SELECT 1
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
WHERE ([a].[PrincipalId] = [a0].[PrincipalId]) AND EXISTS (
SELECT 1
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
WHERE ([a1].[Id] = [a2].[RoleId]) AND ([a3].[Name] = "SomePermission")))) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
To see if a principal has a Role in Roles that has a Permission in Permissions you can use the following Where condition:
var result = await context.Principals
.Where(p => p.Id == "SomeUUID" && p.Roles.Any(r => r.Permissions.Any(x => x.Name == "SomePermission")))
.FirstOrDefaultAsync();
If result is null then either the Principal did not exist or did not have the permission you checked for. If result still needs to have the navigation properties then you can add the include statements originally included in your question.
Probably you are looking for this query:
var hasPermssion = context.Principals
.Where(p => p.Id == "SomeUUID")
.SelectMany(p => p.Roles)
.SelectMany(r => r.Permissions)
.Any(p => p.Name == "SomePermission");
Here is the generated SQL statement:
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
INNER JOIN (
SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
INNER JOIN (
SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
) AS [t0] ON [t].[Id] = [t0].[RoleId]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
How to generate such SQL code:
SELECT TOP(10) c.Id AS CarId, r.FranceAccessoriesCount, r.GermanyAccessoriesCount, r.ItalyAccessoriesCount
FROM [Cars] AS c
OUTER APPLY (SELECT
SUM(CASE WHEN a.ManufacturerCountry = 'France' then 1 ELSE 0 END) as FranceAccessoriesCount,
SUM(CASE WHEN a.ManufacturerCountry = 'Germany' then 1 ELSE 0 END) as GermanyAccessoriesCount,
SUM(CASE WHEN a.ManufacturerCountry = 'Italy' then 1 ELSE 0 END) as ItalyAccessoriesCount
FROM [Accessories] a
WHERE a.CarId = c.Id AND a.[Year] > 1999) r
using LINQ query (EF Core)?
I've tried:
await this.context.Cars
.Take(10)
.Select(c => new
{
CarId = c.Id,
Accessories = this.context.Accessories.Where(a => a.CarId == c.Id && a.Year > 1999)
})
.Select(c => new
{
CarId = c.CarId,
FranceAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "France"),
GermanAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "Germany"),
ItalyAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "Italy")
})
.ToListAsync();
But this doesn't generate OUTER APPLY. Instead it translates to:
SELECT TOP(10) [c].[Id] AS CarId (
SELECT COUNT(*)
FROM [Accessories] AS [a]
WHERE (([a].[CarId] = [c].[Id]) AND [a].[Year] > 1999) AND ([a].[ManufacturerCountry ] = N'France')) AS [FranceAccessoriesCount ], (
SELECT COUNT(*)
FROM [Accessories] AS [a1]
WHERE (([a].[CarId] = [c].[Id]) AND [a1].[Year] > 1999) AND ([a1].[ManufacturerCountry ] = N'Germany')) AS [GermanyAccessoriesCount ], (
SELECT COUNT(*)
FROM [Accessories] AS [a2]
WHERE (([a2].[CarId] = [c].[Id]) AND [a2].[Year] > 1999) AND ([a2].[ManufacturerCountry ] = N'Italy')) AS [ItalyAccessoriesCount]
FROM [Cars] AS [c]
How to write LINQ query which will translate in OUTER APPLY?
EF is a mapper. You tell it what you want using entities as you have them defined and it generates an SQL statement to retrieve and project that data. It may not be optimal in all circumstances but generally it is quite good from a performance perspective.
If the EF query is not returning the data you expect to see, then approach it from the desired data you want to project to and express a question from what projection you want vs. what projection you get.
If the EF query is returning the data you expect, but simply isn't using a CROSS APPLY like you expect, then the answer is that if this is important, don't rely on the EF generated query and instead have it execute a hand-crafted SQL statement.
Sorry, my English is very poor, I hope you understand.
If I use Linq it's correct, but now I want to use Lambda.
TABLE 1
ExamQuestion
TABLE 2
PaperQuestion
No foreign key;
var l = db.ExamQuestions
.Join(db.PaperQuestions, s => s.Id, p => p.QuestionId, (s, p) => new
ExamQuestionList()
{
Id = s.Id,
Question = s.Question,
CateTitle = "aaa",
Option = s.Option,
IsPass = s.IsPass,
Answer = s.Answer,
Difficulty = s.Difficulty,
IsDelete = s.IsDelete,
IsExist = true,
Score = p.Score,
CreateTime = s.CreateTime
})
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Question] AS [Question],
N'aaa' AS [C1],
[Extent1].[Option] AS [Option],
[Extent1].[IsPass] AS [IsPass],
[Extent1].[Answer] AS [Answer],
[Extent1].[Difficulty] AS [Difficulty],
[Extent1].[IsDelete] AS [IsDelete],
cast(1 as bit) AS [C2],
[Extent2].[Score] AS [Score],
[Extent1].[CreateTime] AS [CreateTime]
FROM [dbo].[ExamQuestions] AS [Extent1]
INNER JOIN [dbo].[PaperQuestions] AS [Extent2] ON [Extent1].[Id] = [Extent2].[QuestionId]
I user DefaulifEmpty but invalid, left outer join nest Outermost layer
var l = db.ExamQuestions
.Join(db.PaperQuestions, s => s.Id, p => p.QuestionId, (s, p) => new
ExamQuestionList()
{
Id = s.Id,
Question = s.Question,
CateTitle = "aaa",
Option = s.Option,
IsPass = s.IsPass,
Answer = s.Answer,
Difficulty = s.Difficulty,
IsDelete = s.IsDelete,
IsExist = true,
Score = p.Score,
CreateTime = s.CreateTime
}).DefaultIfEmpty();
SELECT
[Project1].[Id] AS [Id],
[Project1].[Question] AS [Question],
[Project1].[C1] AS [C1],
[Project1].[Option] AS [Option],
[Project1].[IsPass] AS [IsPass],
[Project1].[Answer] AS [Answer],
[Project1].[Difficulty] AS [Difficulty],
[Project1].[IsDelete] AS [IsDelete],
[Project1].[C2] AS [C2],
[Project1].[Score] AS [Score],
[Project1].[CreateTime] AS [CreateTime]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN (SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Question] AS [Question],
[Extent1].[Option] AS [Option],
[Extent1].[Answer] AS [Answer],
[Extent1].[CreateTime] AS [CreateTime],
[Extent1].[Difficulty] AS [Difficulty],
[Extent1].[IsPass] AS [IsPass],
[Extent1].[IsDelete] AS [IsDelete],
[Extent2].[Score] AS [Score],
N'aaa' AS [C1],
cast(1 as bit) AS [C2]
FROM [dbo].[ExamQuestions] AS [Extent1]
INNER JOIN [dbo].[PaperQuestions] AS [Extent2] ON [Extent1].[Id] = [Extent2].[QuestionId] ) AS [Project1] ON 1 = 1
It is a valid sql statement that it was generated by the linq provider and posted by you in the OP..
The most outer table [SingleRowTable1] allows to get always at least a single empty record composed by null fields named with the aliases specified in the statement by the column names prefixed with [Project1] table alias name.
The final join condition AS [Project1] ON 1 = 1 clause bind all rows getted by the inner most projection to the outer most fictitious table ( SELECT 1 AS X ) that get always 1 value annd attach it to each row getted by the innermost projection.
I hope it is a clear explanation..
I am not sure how I can express the following SQL using linq to SQL
SELECT p.*, T1.RecordCount
FROM Person p
INNER JOIN
(
SELECT PersonId, count(1) as RecordCount FROM [PersonView]
WHERE LastName like 'LIS%'
GROUP BY PersonId
) AS T1 ON T1.PersonId = p.PersonId
You can try this:
var innerQuery=from pv in context.PersonView
where pv.LasName.StartWith("LIS")
group pv by pv.PersonId into g
select new{ PersonId=g.Key,RecordCount = g.Count()};
var query= from p in context.Person
join t1 in innerQuery on p.PersonId equals t1.PersonId
select new{p, t1.RecordCount};
Can I do this:
With ZipCodeCTE as
{
select nvl(HH.GeoUSZip5 , **ZipCodeKeyLookUp**(HH.[CityName],HH.[StateName])) as TotalZipCode
from ODSDataArchive.archive.HHJob_Data_201202 HH
}
/* This Is a SELECT INTO statement that inserts
data into [Jobs].[dbo].[FactRPP]*/
SELECT [dbo].[FactJobsDaily].jobdid,
[dbo].[FactJobsDaily].DateKey,
[dbo].[FactJobsDaily].YearMonth,
[dbo].[FactJobsDaily].AccountKey,
[dbo].[FactJobsDaily].BridgeSocKey,
[dbo].[FactJobsDaily].HostSiteKey,
[dbo].[FactJobsDaily].JobClickedCount,
[dbo].[FactJobsDaily].JobResultsPageCount,
(select DZ.ZipCodeKey
from dimensions.dbo.DimZipCode DZ
where DZ.ZipCodeKey IN
(Select CAST(TotalZipCode AS INT)
from ZipCodeCTE))
INTO [Jobs].[dbo].[FactRPP]
from dbo.FactJobsDaily
inner join ODSDataArchive.archive.HHJob_Data_201202
on dbo.FactJobsDaily.JobDID = ODSDataArchive.archive.HHJob_Data_201202.DID
and dbo.FactJobsDaily.datekey = ODSDataArchive.archive.HHJob_Data_201202.datekey
inner join dimensions.dbo.Dimzipcode dzc
on ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 = dimensions.dbo.Dimzipcode.ZipCode
where [dbo].[FactJobsDaily].yearmonth= 201202
and [dbo].[FactJobsDaily].isactivekey = 1
-- and ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 <> ''
-- and ODSDataArchive.archive.HHJob_Data_201202.geoUSZip5 IS NOT NULL
and ODSDataArchive.archive.HHJob_Data_201202.status = 0
and ODSDataArchive.archive.HHJob_Data_201202.CountryName = 'US'
order by [dbo].[FactJobsDaily].jobdid;
Because the CTE translates into a regular query the short answer is yes.