We are using EF 6.1. Despite the improvements from v4, there is often need to help EF in the decision on how to generate SQL to be more efficient. Usually helps to use LINQ in our case and specify joins.
However, now I have a case where I don't know how to do it (besides circumventing the EF completely):
return db.Testlets.Include("TestTasks.TestQuestions.TestAnswers")
.Include("TestTasks.TestQuestions.TestQuestionCriterionGroups.TestQuestionCriterions")
.Include("TestTasks.TestQuestions.Question.Answers")
.Where(x => x.TestId == testId && x.ShownOn.HasValue)
.ToList();
This produces code which is very inefficient. In fact it should be enough and best if EF produced something like this:
SELECT *
FROM TestLet TL
INNER JOIN TestTask TT ON TL.Guid = TT.TestletId
INNER JOIN TestQuestion TQ ON TT.Guid = TQ.TestTaskId
INNER JOIN TestAnswer TA ON TQ.Guid = TA.TestQuestionId
LEFT OUTER JOIN TestQuestionCriterionGroup TQCG ON TQCG.TestQuestionId = TQ.Guid
LEFT OUTER JOIN TestQuestionCriterion TQC ON TQCG.Guid = TQC.TestQuestionCriterionGroupId
INNER JOIN Question Q ON TQ.QuestionId = Q.QuestionId AND Q.IsActive = 1
INNER JOIN Answer A ON Q.QuestionId = A.QuestionId AND A.IsActive = 1
WHERE
TL.TestId='59ADFB3F-16A6-46E0-8054-7F6E83414DC9'
AND TL.ShownOn IS NOT NULL
I came to the point where the following code below (without includes in the end) produced the SQL above, but was selecting just columns of testlets (no Includes were applied, because they were not there and therefore no mapping to EF entities) and I need the whole hierarchy eagerly loaded. When I added the includes in the end, the generated SQL was again horrible and very slow:
(from tl in
db.Testlets.Where(tl => tl.TestId == testId && tl.ShownOn.HasValue)
from tt in db.TestTasks.Where(tt => tl.Guid == tt.TestletId)
from tq in db.TestQuestions.Where(tq => tt.Guid == tq.TestTaskId)
from ta in db.TestAnswers.Where(ta => tq.Guid == ta.TestQuestionId)
from q in db.Questions.Where(q => tq.QuestionId == q.Id)
from a in db.Answers.Where(a => q.Id == a.QuestionId)
from tqcg in
db.TestQuestionCriterionGroups.Where(tqcg => tq.Guid == tqcg.TestQuestionId).DefaultIfEmpty()
from tqc in
db.TestQuestionCriterions.Where(tqc => tqcg.Guid == tqc.TestQuestionCriterionGroupId)
.DefaultIfEmpty()
select tl).Include("TestTasks.TestQuestions.TestAnswers")
.Include("TestTasks.TestQuestions.TestQuestionCriterionGroups.TestQuestionCriterions")
.Include("TestTasks.TestQuestions.Question.Answers")
Does anybody know how to write linq2sql o entities2sql code which would be efficient and has the correct outcome? Or is there only the way of abandoning the EF for this more complex scenarios? If so how to do the mapping back to EF structures (from SQL with joins above) in the most easy way?
In case somebody wants to know more about how to do left joins: https://msdn.microsoft.com/en-us/library/bb397895.aspx
and why Includes do not work on when specified in the query at the beginning: http://blogs.msdn.com/b/alexj/archive/2009/06/02/tip-22-how-to-make-include-really-include.aspx
UPDATE: the gist with the generated sql: https://gist.github.com/Ondrashx/d0347fc807f0f7fbdf46
DbContext.Database.Log.