Optimizing LINQ To Entities query - entity-framework

OK, let's say that I have two entities, A and B. For each user, there may be 0 or more A, with a unique combination of UserId (foreign key), GroupName, and Name properties. Entity A also has an "Value" property. Also for each user, there may be 0 or more B, with a UserID (again, a foreign key) and an "Occurred" property.
My task is to find all the B which have an Occurred property of more than 7 days ago OR have an Occurred property more than now - the number of hours in a particular A property. This code seems to work perfectly:
DateTime now = DateTime.UtcNow;
DateTime expired = now - TimeSpan.FromDays(7d);
using (DatabaseContext context = DatabaseContext.Create())
{
IQueryable<A> aQ = context.As.Where(a => a.GroupName == "Notifications" && s.Name == "Retention");
IQueryable<B> bQ = context.Bs.Where(
n => aQ.Any(a => a.UserId == b.UserId) ?
b.Occurred < EntityFunctions.AddHours(now, -aQ.FirstOrDefault(a => a.UserId == b.UserId).Value) :
b.Occurred < expired);
IList<B> bs = bQ.ToList();
// ...
}
This produces a SQL query along these lines:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[OCCURRED] AS [OCCURRED],
[Extent1].[USERID] AS [USERID],
FROM [dbo].[B] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[GROUPNAME] AS [GROUPNAME],
[Extent2].[NAME] AS [NAME],
[Extent2].[USERID] AS [USERID],
[Extent2].[VALUE] AS [VALUE],
FROM [dbo].[A] AS [Extent2]
WHERE (N'Notifications' = [Extent2].[GROUPNAME]) AND (N'Retention' = [Extent2].[NAME]) AND ([Extent2].[USERID] = [Extent1].[USERID]) ) AS [Element1]
OUTER APPLY (SELECT TOP (1)
[Extent3].[GROUPNAME] AS [GROUPNAME],
[Extent3].[NAME] AS [NAME],
[Extent3].[USERID] AS [USERID],
[Extent3].[VALUE] AS [VALUE],
FROM [dbo].[A] AS [Extent3]
WHERE (N'Notifications' = [Extent3].[GROUPNAME]) AND (N'Retention' = [Extent3].[NAME]) AND ([Extent3].[USERID] = [Extent1].[USERID]) ) AS [Element2]
WHERE (CASE WHEN ( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[A] AS [Extent4]
WHERE (N'Notifications' = [Extent4].[GROUPNAME]) AND (N'Retention' = [Extent4].[NAME]) AND ([Extent4].[USERID] = [Extent1].[USERID])
)) THEN CASE WHEN ( CAST( [Extent1].[OCCURRED] AS datetime2) < (DATEADD (hours, -([Element1].[VALUE]), #p__linq__0))) THEN cast(1 as bit) WHEN ( NOT ( CAST( [Extent1].[OCCURRED] AS datetime2) < (DATEADD (hours, -([Element2].[VALUE]), #p__linq__0)))) THEN cast(0 as bit) END WHEN ([Extent1].[OCCURRED] < #p__linq__1) THEN cast(1 as bit) WHEN ( NOT ([Extent1].[OCCURRED] < #p__linq__1)) THEN cast(0 as bit) END) = 1
Please note that I've hacked the code and SQL query down from the actual stuff, so this may not be the perfect representation. But I hope it gets the point across: this looks a little hairy, at least in the way that the query is repeatedly checking A for matching groupname, name, and user. But I'm no Database expert. What I do know is that one observed execution ran roughly 2.5 seconds.
Is there a better way of going about this?
Thanks for any input!
---- SOLUTION ----
Thanks to Gert for this.
The code's query ended up like this:
var bQ =
from b in context.Bs
let offset = aQ.FirstOrDefault(a => a.UserId == b.UserId)
let expiration = (null != offset) ? EntityFunctions.AddHours(now, -offset.Value) : expired
where b.Occurred < expiration
select b;
Which is almost exactly what Gert suggested. The new SQL query looks like this:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[OCCURRED] AS [OCCURRED],
[Extent1].[USERID] AS [USERID]
FROM [dbo].[B] AS [Extent1]
OUTER APPLY (SELECT TOP (1)
[Extent2].[GROUPNAME] AS [GROUPNAME],
[Extent2].[NAME] AS [NAME],
[Extent2].[USERID] AS [USERID],
[Extent2].[VALUE] AS [VALUE]
FROM [dbo].[A] AS [Extent2]
WHERE (N'Notifications' = [Extent2].[GROUPNAME]) AND (N'Retention' = [Extent2].[NAME]) AND ([Extent2].[USERID] = [Extent1].[USERID]) ) AS [Element1]
WHERE CAST( [Extent1].[OCCURRED] AS datetime2) < (CASE WHEN ([Element1].[ID] IS NOT NULL) THEN DATEADD (hour, -([Element1].[VALUE]), #p__linq__0) ELSE #p__linq__1 END)

I think that this will query the A table only once:
from b in context.Bs
let offset = aQ.FirstOrDefault(a => a.UserId == b.UserId).Value
let dd = offset.HasValue
? EntityFunctions.AddHours(now, -offset)
: expired
where b.Occurred < dd

Related

Querying for any entries after traversing multiple conditional includes in EF Core 5

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

View query without sub selecting T-SQL

so I'm trying to build a view query but I keep failing using only joins so I ended up with this deformation.. Any tips on how I can write this query so I don't have to use 6 subselects?
The FeeSum and PaymentSum can be null, so ideally I do not want those in my result set and I also wouldn't like results where the FeeSum and the PaymentSum are equal.
Quick note: client is the table where the clients informations are stored (name, adress, etc..)
customer has a fk on client and is kind of a shell table for the client that store more information for the client,
payment is a list of all payments a customer did,
order is a list of all orders a customer did.
The goal is to get a list where we can track which customer has open fees to pay, based on the orders. It's a legacy project so don't ask why people can order before paying :)
SELECT
cu.Id as [CustomerId]
, CASE
WHEN cl.IsPerson = 1
THEN cl.[AdditionalName] + ' ' + cl.[Name]
ELSE cl.AdditionalName
END as [Name]
, cl.CustomerNumber
, (SELECT SUM(o.Fee) FROM [publication].[Order] o WHERE o.[State] = 2 AND o.CustomerId = cu.Id) as [FeeSum]
, (SELECT SUM(p.Amount) FROM [publication].[Payment] p WHERE p.CustomerId = cu.Id) as [PaymentSum]
, (SELECT MAX(o.OrderDate) FROM [publication].[Order] o WHERE o.[State] = 2 AND o.CustomerId = cu.Id) as [LastOrderDate]
, (SELECT MAX(p.PaymentDate) FROM [publication].[Payment] p WHERE p.CustomerId = cu.Id) as [LastPaymentDate]
, (SELECT MAX(f.Created) FROM [client].[File] f WHERE f.TemplateName = 'Reminder' AND f.ClientId = cl.Id) as [LastReminderDate]
, (SELECT MAX(f.Created) FROM [client].[File] f WHERE f.TemplateName = 'Warning' AND f.ClientId = cl.Id) as [LastWarningDate]
FROM
[publication].[Customer] cu
JOIN
[client].[Client] cl
ON cl.Id = cu.ClientId
WHERE
cu.[Type] = 0
Thanks in advance and I hope I didn't do anything wrong.
Kind regards
You could rewrite the correlated subqueries to instead use joins:
SELECT
cu.Id AS [CustomerId],
CASE WHEN cl.IsPerson = 1
THEN cl.[AdditionalName] + ' ' + cl.[Name]
ELSE cl.AdditionalName END AS [Name],
cl.CustomerNumber,
o.FeeSum,
p.PaymentSum,
o.LastOrderDate,
p.LastPaymentDate,
f.LastReminderDate,
f.LastWarningDate
FROM [publication].[Customer] cu
INNER JOIN [client].[Client] cl
ON cl.Id = cu.ClientId
INNER JOIN
(
SELECT CustomerId, SUM(Fee) AS [FeeSum], MAX(OrderDate) AS [LastOrderDate]
FROM [publication].[Order]
WHERE o.[State] = 2
GROUP BY CustomerId
) o
ON o.CustomerId = cu.Id
INNER JOIN
(
SELECT CustomerId, SUM(Amount) AS [PaymentSum], MAX(PaymentDate) AS [LastPaymentDate]
FROM [publication].[Payment]
WHERE o.[State] = 2
GROUP BY CustomerId
) p
ON p.CustomerId = cu.Id
INNER JOIN
(
SELECT ClientId,
MAX(CASE WHEN TemplateName = 'Reminder' THEN Created END) AS [LastReminderDate],
MAX(CASE WHEN TemplateName = 'Warning' THEN Created END) AS [LastWarningDate]
FROM [client].[File]
GROUP BY ClientId
) f
ON f.ClientId = cl.Id
WHERE
cu.[Type] = 0;

History of Available Stock in SAP B1

How can I find what was the available stock on a specific date in the past?
I know how to get OnHand for any date in the past:
SELECT LocType, LocCode, ItemCode, SUM(InQty-OutQty) [OnHand]
From OIVL
Where DocDate <= '7/7/2017'
AND ITEMCODE = 'xyz'
Group by LocType, LocCode, ItemCode
But this does not account for what was committed. It shows what was on hand, but not what was actually available (OnHand - Committed = Available). So, how could I get to this "Available" number for dates in the past?
Thanks!
In case this helps anyone else.... here is an example of how to get your item quantity and cost history in SAP BusinessOne. Pass the date and item you want to see history for into the function. If you omit item code it returns all items.
CREATE FUNCTION [dbo].[ufnStockAndCostHistory]
(
#EndDate DATETIME, #ItemCode NVARCHAR(255) = 'All'
)
RETURNS TABLE
AS
RETURN
(
WITH Cost AS (
SELECT
OINM.TransNum,
OINM.ItemCode,
OINM.Warehouse,
OINM.Balance,
OINM.CreateDate
FROM OINM (NOLOCK)
JOIN OITM (NOLOCK) ON OITM.ItemCode = OINM.ItemCode
JOIN OITB (NOLOCK) ON OITM.ItmsGrpCod=OITB.ItmsGrpCod
WHERE OINM.CreateDate <= #EndDate
),
FinalCostByWarehouse AS (
SELECT *
FROM Cost
WHERE TransNum = (
SELECT MAX(TransNum)
FROM Cost sub
WHERE Cost.ItemCode = sub.ItemCode
AND Cost.Warehouse = sub.Warehouse)
),
Quantity AS (
SELECT
OINM.ItemCode,
OINM.Warehouse,
SUM(OINM.InQty) - SUM(OINM.OutQty) [Qty]
FROM OINM (NOLOCK)
JOIN OITM (NOLOCK) ON OITM.ItemCode = OINM.ItemCode
JOIN OITB (NOLOCK) ON OITM.ItmsGrpCod=OITB.ItmsGrpCod
WHERE OINM.CreateDate <= #EndDate
group by OINM.ItemCode, OINM.Warehouse
)
SELECT
OITW.ItemCode,
OITW.WhsCode,
c.Balance [Cost],
q.Qty [Quantity]
FROM OITW
LEFT JOIN FinalCostByWarehouse c ON OITW.ItemCode = c.ItemCode
AND OITW.WhsCode = c.Warehouse
LEFT JOIN Quantity q ON OITW.ItemCode = q.ItemCode
AND OITW.WhsCode = q.Warehouse
WHERE (OITW.ItemCode = #ItemCode
OR #ItemCode = 'All')
AND (ISNULL(c.Balance,0) > 0
OR ISNULL(q.Qty,0) > 0)
)
Usage would look like:
SELECT *
FROM ufnStockAndCostHistory('9/15/2017','B107A-12HB')

Why EF 6.13 left outer join with Lambda DefaultIfEmpty is Invalid?

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..

Entity Framework ... Four queries into one

I am using EF5 and finding questions as follows:
context.Questions
.OrderBy(x => Guid.NewGuid())
.Where(x => x.Difficulty == difficulty && x.Format == format);
Each Question has a field named duration with values (2, 4, 6 and 8) in minutes.
I have the following int array: { 4, 0, 1, 2 } which means:
Get 4 questions of 2 minutes, 0 of 4 minutes, 1 of 6 minutes and 2 of 8 minutes.
After .OrderBy and .Where I need to get 7 questions as mentioned.
Is there a way to do this without loading all questions or using 4 queries?
Thank You,
Miguel
This is kind of hardcoded - but you get the idea, and solves your problem explicitly. You should be able to convert it to whatever you need from here.
var questions = context.Questions .OrderBy(x => Guid.NewGuid())
.Where(x => x.Difficulty == difficulty && x.Format == format);
var selectedQuestions = questions.Where(q => q.Duration == 2).Take(questionArray[0])
.Union(questions.Where(q => q.Duration == 4).Take(questionArray[1]))
.Union(questions.Where(q => q.Duration == 6).Take(questionArray[2]))
.Union(questions.Where(q => q.Duration == 8).Take(questionArray[3]));
Since you're never enumerating the queryable, EF will do all of these unions in sql, and get all the data in a single call.
Produced SQL:
SELECT [Distinct3].[C1] AS [C1],
[Distinct3].[C2] AS [C2],
[Distinct3].[C3] AS [C3],
[Distinct3].[C4] AS [C4]
FROM (SELECT DISTINCT [UnionAll3].[C1] AS [C1],
[UnionAll3].[C2] AS [C2],
[UnionAll3].[C3] AS [C3],
[UnionAll3].[C4] AS [C4]
FROM (SELECT [Distinct2].[C1] AS [C1],
[Distinct2].[C2] AS [C2],
[Distinct2].[C3] AS [C3],
[Distinct2].[C4] AS [C4]
FROM (SELECT DISTINCT [UnionAll2].[C1] AS [C1],
[UnionAll2].[C2] AS [C2],
[UnionAll2].[C3] AS [C3],
[UnionAll2].[C4] AS [C4]
FROM (SELECT [Distinct1].[C1] AS [C1],
[Distinct1].[C2] AS [C2],
[Distinct1].[C3] AS [C3],
[Distinct1].[C4] AS [C4]
FROM (SELECT DISTINCT [UnionAll1].[Id] AS [C1],
[UnionAll1].[Duration] AS [C2],
[UnionAll1].[Difficulty] AS [C3],
[UnionAll1].[Format] AS [C4]
FROM (SELECT TOP (4) [Project1].[Id] AS [Id],
[Project1].[Duration] AS [Duration],
[Project1].[Difficulty] AS [Difficulty],
[Project1].[Format] AS [Format]
FROM (SELECT NEWID() AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Duration] AS [Duration],
[Extent1].[Difficulty] AS [Difficulty],
[Extent1].[Format] AS [Format]
FROM [dbo].[Questions] AS [Extent1]
WHERE ([Extent1].[Difficulty] = #p__linq__0)
AND ([Extent1].[Format] = #p__linq__1)
AND (2 = [Extent1].[Duration])) AS [Project1]
ORDER BY [Project1].[C1] ASC
UNION ALL
SELECT TOP (0) [Project3].[Id] AS [Id],
[Project3].[Duration] AS [Duration],
[Project3].[Difficulty] AS [Difficulty],
[Project3].[Format] AS [Format]
FROM (SELECT NEWID() AS [C1],
[Extent2].[Id] AS [Id],
[Extent2].[Duration] AS [Duration],
[Extent2].[Difficulty] AS [Difficulty],
[Extent2].[Format] AS [Format]
FROM [dbo].[Questions] AS [Extent2]
WHERE ([Extent2].[Difficulty] = #p__linq__2)
AND ([Extent2].[Format] = #p__linq__3)
AND (4 = [Extent2].[Duration])) AS [Project3]
ORDER BY [Project3].[C1] ASC) AS [UnionAll1]) AS [Distinct1]
UNION ALL
SELECT TOP (1) [Project7].[Id] AS [Id],
[Project7].[Duration] AS [Duration],
[Project7].[Difficulty] AS [Difficulty],
[Project7].[Format] AS [Format]
FROM (SELECT NEWID() AS [C1],
[Extent3].[Id] AS [Id],
[Extent3].[Duration] AS [Duration],
[Extent3].[Difficulty] AS [Difficulty],
[Extent3].[Format] AS [Format]
FROM [dbo].[Questions] AS [Extent3]
WHERE ([Extent3].[Difficulty] = #p__linq__4)
AND ([Extent3].[Format] = #p__linq__5)
AND (6 = [Extent3].[Duration])) AS [Project7]
ORDER BY [Project7].[C1] ASC) AS [UnionAll2]) AS [Distinct2]
UNION ALL
SELECT TOP (2) [Project11].[Id] AS [Id],
[Project11].[Duration] AS [Duration],
[Project11].[Difficulty] AS [Difficulty],
[Project11].[Format] AS [Format]
FROM (SELECT NEWID() AS [C1],
[Extent4].[Id] AS [Id],
[Extent4].[Duration] AS [Duration],
[Extent4].[Difficulty] AS [Difficulty],
[Extent4].[Format] AS [Format]
FROM [dbo].[Questions] AS [Extent4]
WHERE ([Extent4].[Difficulty] = #p__linq__6)
AND ([Extent4].[Format] = #p__linq__7)
AND (8 = [Extent4].[Duration])) AS [Project11]
ORDER BY [Project11].[C1] ASC) AS [UnionAll3]) AS [Distinct3];
Why not just get 4 (or max of the array) questions of each type and do the filtering on the client? With 16 results it should be cheap - you can use linq grouping to group them by minutes and then it should be easy. Another interesting point here is ordering... If you have more than 4 questions but you always order them when selecting would you ever see any other questions that just first 4?
My query is a little bit more complex. Basically, I ended up with the following:
context.Questions
.OrderBy(x => Guid.NewGuid())
.Where(x =>
x.AccessLevel >= accessLevel &&
x.Enabled == true &&
x.QuestionFormat == questionFormat
x.Theme.Id == themeId
)
.Select(x => new {
Answers = x.Answers.Select(y => new {
Correct = y.Correct,
Packs = y.Packs.SelectMany(z => z.Files, (z, v) => new {
Id = z.Id,
File = new { Key = v.Key, Mime = v.Mime }
}),
Text = y.Text
}),
Duration = x.Duration,
Note = x.Note,
Text = x.Text,
Packs = x.Packs.SelectMany(y => y.Files, (y, z) => new {
Id = y.Id,
File = new { Key = z.Key, Mime = z.Mime }
})
})
.GroupBy(x => x.Duration);
So I am ordering the questions randomly and filtering them.
Then I get, for each question, its answers ...
Each question and answer have some files associated with them.
I have all files in one table. This is why I have Packs and Files in my query.
I am only loading the Files Keys and Mimes. Not the data.
So you suggestion is to get all questions grouped by time, right?
In fact, after do the AccessLevel / Enabled / QuestionFormat / Theme filtering I will never have so many questions ...
So I can group them by Duration as I did in the end of this query.
What would be a fast way to take the number of questions of each duration according to the array I posted in the beginning?
Thank You,
Miguel