我有一个设计,我们存储给定用户的脚本问题的答案。 一个脚本可以有很多问题,每个问题可以由给定用户多次回答。
MS SQL表看起来(删除额外的细节)或多或少像:
-Scripts
ScriptId int (PK, identity)
-ScriptQuestions
ScriptId int (PK)
QuestionId int (PK)
-Questions
QuestionId int (PK, identity)
QuestionText varchar
-Answers
AnswerId int (PK, identity)
QuestionId int
UserId int
AnswerText varchar
我想查询此数据库中的给定脚本和给定用户,并获取所有问题以及为每个问题提供的最后答案(如果有)。 在T-SQL中我会做这样的事情:
SELECT
sq.QuestionId,
q.QuestionText,
la.AnswerText
FROM
ScriptQuestions sq
ON s.ScriptId = sq.ScriptId
INNER JOIN Questions q
ON sq.QuestionId = q.QuestionId
LEFT OUTER JOIN (
SELECT
QuestionId,
AnswerText
FROM Answers
WHERE AnswerId IN (
SELECT
MAX(AnswerId) LastAnswerId
FROM Answers
WHERE UserId = @userId
GROUP BY QuestionId
)
) la
ON q.QuestionId = la.QuestionId
WHERE
sq.ScriptId = @scriptId
(未经测试,但我认为这与我的做法很接近)
我在MVC 3应用程序上使用LinqToEF,并获得我使用的结果:
var questions = from sq in script.ScriptQuestions
select new QuestionsAnswers
{
QuestionId = sq.QuestionId,
QuestionText = sq.Question.QuestionText,
LastAnswer = sq.Question.Answers
.Where(a => a.UserId == userId)
.OrderByDescending(a => a.AnswerId)
.Select(a => a.AnswerText)
.FirstOrDefault()
};
我确实得到了相同的结果但是当我从VS 2010运行Intellitrace分析器时,我可以看到linq将其转换为在脚本上发送每个问题的SELECT
语句,然后另一个SELECT
每个答案的陈述。因此,如果脚本有20个问题,它将至少查询数据库40次,而不是像上面那样只发送一个SQL语句。
我尝试更改创建LinqToEF语句的方式,但我无法克服n SELECT
语句问题。
这不是正确的方法,或者是这样吗?
答案 0 :(得分:1)
我的猜测是你的查询在内存中使用LINQ to Objects和延迟加载导航属性,因为你的查询以script.ScriptQuestions
开头,显然不是IQueryable
。因此,集合在内存中进行迭代,对于每个条目,您都可以访问sq.Question
和sq.Question.Answers
导航属性。如果每次访问这些属性时都启用了延迟加载,则会向DB发出新查询以填充属性。 sq.Question.Answers
集合上的过滤器在完整集合的内存中执行。
您可以尝试通过以下方式进行更改:
var questions = from sq in context.ScriptQuestions
where sq.ScriptId == script.ScriptId
select new QuestionsAnswers
{
QuestionId = sq.QuestionId,
QuestionText = sq.Question.QuestionText,
LastAnswer = sq.Question.Answers
.Where(a => a.UserId == userId)
.OrderByDescending(a => a.AnswerId)
.Select(a => a.AnswerText)
.FirstOrDefault()
};
这应该只是一个单一的数据库查询,因为它现在是带有IQueryable
的LINQ to Entities。