我可以让这个Linq到EF更有效吗?

时间:2011-11-10 17:32:57

标签: c# sql entity-framework linq-to-sql linq-to-entities

我有一个设计,我们存储给定用户的脚本问题的答案。 一个脚本可以有很多问题,每个问题可以由给定用户多次回答。

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语句问题。

这不是正确的方法,或者是这样吗?

1 个答案:

答案 0 :(得分:1)

我的猜测是你的查询在内存中使用LINQ to Objects和延迟加载导航属性,因为你的查询以script.ScriptQuestions开头,显然不是IQueryable。因此,集合在内存中进行迭代,对于每个条目,您都可以访问sq.Questionsq.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。