Запрос службы WCF RIA медленный - PullRequest
3 голосов
/ 20 сентября 2011

У меня есть доменная служба WCF RIA Services, поддерживаемая Entity Framework. Модель сущностей довольно сложна. Как правило, производительность подходит для большинства операций.

Однако мой клиент Silverlight включает в себя представление мастера / сведений Основной вид - это список объектов. При щелчке между объектами для их выбора на сервере запускается метод IQueryable GetEntity () , и это занимает в среднем 5 секунд - недопустимое количество времени.

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

ServerGotRequest:   15:59:44.545
ServerBeginResponse:15:59:48.836
ServerDoneResponse: 15:59:48.836
ClientBeginResponse:15:59:48.836
ClientDoneResponse: 15:59:48.836
Overall Elapsed:    00:00:04.2940000

Возвращенные данные, как они появляются на проводе, также довольно малы. В этом примере около 6 КБ.

ОК - моя проблема на стороне сервера. Запрос в EF довольно прост, с одной оговоркой: у нас есть ~ 25 .Включить операторы для ввода связанных сущностей. .Include включает объекты до 4 уровней глубины (например, .Include ("1.2.3.4")).

Так что моя следующая мысль заключается в том, что БД работает медленно. Нет, я запускаю SQL Profiler и вижу (по общему признанию, ужасно), что SQL выполняется в среднем за 0,15 секунды. Возвращенные данные не так уж плохи - 3 строки по 275 столбцов.

Итак:

  • Мое узкое место на сервере
  • Я выбираю несколько связанных объектов посредством прямого запроса EF
  • Сгенерированный SQL некрасив, но достаточно быстрый

Так почему я медленный? Как я могу отладить это?

Если я установил точку останова в конце моего IQueryable GetEntity () метода, то, по-видимому, после выхода из этого метода потребуется около 3 секунд, прежде чем фактический запрос появится в SQL Profiler. WTF?

Обратите внимание, что я предварительно сгенерировал свои представления Entity Framework и попытался использовать скомпилированный запрос, чтобы исключить время разогрева EF. Без разницы.

Я ценю любую помощь в этом вопросе. Заранее спасибо.

Ответы [ 4 ]

3 голосов
/ 22 сентября 2011

Я понимаю ответы, говорящие мне, чтобы я упаковывал свои данные, а не загружал всю сущность и т. Д .; и в целом я согласен с таким подходом. Однако в этом сценарии мы выбираем одну сущность - поскольку она поступает из нормализованной базы данных, она предполагает объединение нескольких таблиц.

В этом точном примере ужасный запрос, сгенерированный EF, все еще выполняется менее чем за 0,2 секунды. Если бы я захотел написать запрос, он, вероятно, снова занял бы 1/10. Как это, это определенно достаточно быстро. Размер всей сущности на проводе составляет <6Kb; что, опять же, я считаю достаточно маленьким. </p>

Так что для меня, чтобы группировать эту единственную сущность по нескольким запросам, просто не работает в этом примере. Ясно, что если бы я использовал прямой ADO.NET и веб-сервисы / WCF, у меня не было бы этой проблемы. Во всяком случае, на ответ:

Я уже говорил, что компиляция запроса EF не помогла. Однако, возможно, это связано с тем, что RIA Services применяет условие «Где EntityID = ID» поверх моего исходного запроса и удаляет мой скомпилированный запрос.

Если я просто сделаю это в службе моего домена:

(from e in ctx.Entities
.Include("SubEntity")
.Include("SubEntity.SubEntity")
// and so on, X20...
where e.EntityID == id
select e).FirstOrDefault();

Эта строка кода выполняется почти все время, но SQL работает очень быстро. Просто медленно добраться до SQL Server. Поэтому для меня это означает, что EF требуется много времени для генерации запроса.

Чтобы устранить проблему, я сделал предварительно скомпилированный запрос, который выбирает мою сущность напрямую по ID и материализует ее, а не возвращает IQueryable:

 private static Func<DataContext, Guid, Entity> getEntityByEntityID =
        CompiledQuery.Compile<DataContext, Guid, Entity>(
        (ctx, id) => (from e in ctx.Entities
                        .Include("SubEntity")
                        .Include("SubEntity.SubEntity")
                        // and so on, X20...
                      where e.EntityID == id
                      select e).FirstOrDefault());

Затем я предоставляю новую операцию в моей доменной службе для использования скомпилированного запроса:

public Entity GetEntityByEntityID(Guid entityID)
    {
        return getEntityByEntityID(this.ObjectContext, entityID);
    }

Результат: теперь он немного медленнее при первом вызове. Для последующих вызовов операция в среднем составляет около 0,5 секунды для всего вызова службы.

1 голос
/ 20 сентября 2011

Для таких вещей, как я думаю, вам, как правило, лучше , а не , используя Include. Загрузите ваш основной объект в Silverlight одним вызовом, а затем загрузите другие включенные элементы по мере необходимости.

Даже если вам в конечном итоге понадобятся все из них в представлении, вы, вероятно, получите лучшую производительность, потянув их в меньших порциях и обновив представление по мере их получения. Это определенно сделает работу с пользователем лучше, чем ожидание запроса.

0 голосов
/ 20 сентября 2011

В Службах WCF RIA - вы должны помнить, что это модель на стороне клиента, которая загружается в первую очередь ПЕРЕД выполнением обратного вызова. Эта модель на стороне клиента НЕ является EF, а просто отображает, как выглядит EF на стороне сервера. Службы RIA WCF сначала загружают эту модель, а затем отправляют обратный вызов со ссылкой на объекты в базовой модели.

Я бы посоветовал вам взглянуть на инфраструктуру Rx для использования нескольких асинхронных вызовов, прежде чем вернуться к клиенту пользовательского интерфейса с мысленно выполненной работой. Используя Rx, вы можете вызвать несколько асинхронных методов и дождаться завершения всех, прежде чем вернуться в интерфейс. Каждый вызов загружает клиентскую модель небольшими битами, что позволяет выполнять работу параллельно перед возвратом. Помните, что это архитектура SOA, и мне легко (обращаясь к себе) вернуться к дизайну блокировки вызовов.

(Task Parrallel Library (TPL) также является другим способом, но пробивается в SL5. Я не настолько знаком с TPL и нюансами между тем, когда использовать RX и / или TPL.)

Я использовал эту стратегию для перетаскивания данных вниз по иерархическому дереву данных, которые я возвращаю - сначала задание - это верхний уровень, затем нижний уровень и т. Д. Помните, что свойства навигации на клиенте - не более чем запросы linq под обложками, где первичный ключ используется для фильтрации по внешнему ключу. Модель на стороне клиента (на данный момент это не модель EF, а что-то близкое к ней) также имеет ограничения ссылочной целостности, но они применяются только при вставке записей, а не ЗАГРУЗКИ записей.

0 голосов
/ 20 сентября 2011

То, как вы это объясняете, - это сериализация / десериализация проблемы графа.Кроме того, у нас были странные проблемы, например, когда SQL при запуске в Analyzer запускался быстро, а из EF он работал медленно.Попробуйте восстановить статистику по таблицам

...