Могу ли я сделать этот Linq to EF более эффективным? - PullRequest
2 голосов
/ 10 ноября 2011

У меня есть дизайн, в котором мы храним ответы на вопросы сценариев заданных пользователей.Один сценарий может иметь много вопросов, и каждый пользователь может ответить на каждый вопрос несколько раз.

Таблицы 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

(не проверено, но я думаю, что это близко к тому, что я бы сделал)

Я использую LinqToEF в приложении MVC 3,и чтобы получить те результаты, которые я использовал:

        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()
                                   };

И я получаю те же результаты, НО, когда я запускаю профилировщик Intellitrace из VS 2010, я вижу, что linq преобразует это в отправку оператора SELECT для КАЖДОГОВОПРОС о сценарии, а затем ДРУГОЕ SELECT утверждение для каждого ответа.Поэтому, если в сценарии содержится 20 вопросов, он будет запрашивать базу данных не менее 40 раз вместо отправки только одного оператора SQL, как указано выше.

Я пытался изменить способ создания оператора LinqToEF, но не смогчтобы преодолеть проблему n SELECT операторов.

Это не может быть правильным путем или нет?

1 Ответ

1 голос
/ 10 ноября 2011

Я предполагаю, что ваш запрос использует LINQ to Objects в памяти в сочетании с отложенной загрузкой свойств навигации, потому что ваш запрос начинается с script.ScriptQuestions, что, по-видимому, не IQueryable.Таким образом, коллекция повторяется в памяти, и для каждой записи вы получаете доступ к свойствам навигации sq.Question и sq.Question.Answers.Если отложенная загрузка включена каждый раз, когда вы получаете доступ к этим свойствам, в БД выдается новый запрос для заполнения свойств.Ваш фильтр для коллекции 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()
                               };

Это должен быть только один запрос к базе данных.потому что теперь это LINQ to Entities с IQueryable.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...