我可以让这个 Linq to EF 更加高效吗?
我有一个设计,我们存储给定用户对脚本问题的答案。 一个脚本可以有许多问题,每个问题可以由给定用户多次回答。
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
语句问题。
这不可能是正确的方法,不是吗?
I have a design where we store answers to scripts's questions by given users.
One script can have many questions and each question can be answered multiple times by a given user.
The MS SQL tables look (removing extra details) more or less like:
-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
I would like to query this database for a given script and a given user and obtain all questions and the last answer provided for each question (if any).
In T-SQL I would do something like this:
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
(untested, but I think it's close to what I would do)
I am using LinqToEF on an MVC 3 application, and to get those results I used:
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()
};
And I do get the same results BUT when I run the Intellitrace profiler from VS 2010 I can see that linq translates this into sending a SELECT
statement for EVERY QUESTION on the script and then ANOTHER SELECT
statement for every answer. So if the scripts has 20 questions, it would be querying the database at least 40 times instead of sending just one SQL statement as above.
I tried to change the way I create my LinqToEF statement but I was not able to overcome the n SELECT
statements issue.
This cannot be the right way, or is it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我的猜测是,您的查询使用内存中的 LINQ to Objects 与导航属性的延迟加载相结合,因为您的查询以
script.ScriptQuestions
开头,这显然不是IQueryable
。因此,该集合会在内存中迭代,并且对于每个条目,您都可以访问 sq.Question 和 sq.Question.Answers 导航属性。如果每次访问这些属性时都启用延迟加载,则会向数据库发出新查询以填充属性。对sq.Question.Answers
集合的过滤器是在内存中对完整集合执行的。您可以尝试按以下方式更改它:
这应该只是一个数据库查询,因为现在它是带有
IQueryable
的 LINQ to Entities。My guess is that your query uses LINQ to Objects in memory in combination with lazy loading of the navigation properties because your query starts with
script.ScriptQuestions
which is apparently not anIQueryable
. So the collection is iterated in memory and for every entry you access thesq.Question
andsq.Question.Answers
navigation properties. If lazy loading is enabled each time you access these properties a new query is issued to the DB to populate the properties. Your filter on thesq.Question.Answers
collection is performed in memory on the full collection.You can try to change it the following way:
This should be only one single database query because now it's LINQ to Entities with
IQueryable
.