Сколько Включить я могу использовать для ObjectSet в EntityFramework, чтобы сохранить производительность?

Я использую следующий запрос LINQ для моей страницы профиля:

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .Include("UserInterests.Interest")
                        .Include("UserMessengers.Messenger")
                        .Include("UserFriends.User.UserSkills.Skill")
                        .Include("UserFriends1.User1.UserSkills.Skill")
                        .Include("UserFriends.User.UserIdeas")
                        .Include("UserFriends1.User1.UserIdeas")
                               where u.UserId == userId
                               select u;

Он имеет длинный граф объектов и использует много включений. Сейчас он работает отлично, но когда на сайте много пользователей, сильно ли это повлияет на производительность?

Должен ли я сделать это по-другому?

5 ответов

Решение

Запрос с включениями возвращает один набор результатов, а количество включений влияет на передачу большого набора данных с сервера базы данных на веб-сервер. Пример:

Предположим, у нас есть сущность Customer (Id, Name, Address) и сущность Order (Id, CustomerId, Date), Теперь мы хотим запросить у клиента ее заказы:

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);

Результирующий набор данных будет иметь следующую структуру:

 Id | Name | Address | OrderId | CustomerId | Date 
---------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1.
  1 |  A   |   XYZ   |    2    |     1      | 2.1.

Это означает, что Cutomers данные повторяются для каждого Order, Теперь давайте расширим пример с другими сущностями - 'OrderLine (Id, OrderId, ProductId, Количество) and Продукт (Id, Name)`. Теперь мы хотим запросить у клиента его заказы, строки заказа и продукты:

var customer = context.Customers
                      .Include("Orders.OrderLines.Product")
                      .SingleOrDefault(c => c.Id == 1);

Результирующий набор данных будет иметь следующую структуру:

 Id | Name | Address | OrderId | CustomerId | Date | OrderLineId | LOrderId | LProductId | Quantity | ProductId | ProductName
------------------------------------------------------------------------------------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     1       |    1     |     1      |    5     |    1      |     AA
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     2       |    1     |     2      |    2     |    2      |     BB
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     3       |    2     |     1      |    4     |    1      |     AA
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     4       |    2     |     3      |    6     |    3      |     CC

Как вы можете видеть, данные сильно дублируются. В общем, каждый включает в себя ссылки на навигационные свойства (Product в примере) добавит новые столбцы и каждый из них будет включать в свойство навигации коллекции (Orders а также OrderLines в примере) добавит новые столбцы и продублирует уже созданные строки для каждой строки во включенной коллекции.

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

Пример отдельных запросов:

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);
var orderLines = context.OrderLines
                        .Include("Product")
                        .Where(l => l.Order.Customer.Id == 1)
                        .ToList();

Пример LoadProperty:

var customer = context.Customers
                      .SingleOrDefault(c => c.Id == 1);
context.LoadProperty(customer, c => c.Orders);

Также вы всегда должны загружать только те данные, которые вам действительно нужны.

Редактировать: я только что создал предложение на Data UserVoice для поддержки дополнительной стратегии загрузки, при которой загруженные данные будут передаваться в дополнительном наборе результатов (создаваемом отдельным запросом в рамках одного и того же обращения к базе данных). Если вы находите это улучшение интересным, не забудьте проголосовать за предложение.

Вы можете улучшить производительность многих включений , создав 2 или более небольших запроса данных из базы данных, как показано ниже.

Согласно моему опыту,Only может дать максимум 2 включения на запрос, как показано ниже. Более того, это даст действительно плохую производительность.

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .FirstOrDefault();

 userData = from u in db.Users
                    .Include("UserFriends.User.UserSkills.Skill")
                    .Include("UserFriends1.User1.UserSkills.Skill")
                    .FirstOrDefault();

Выше принесет небольшой набор данных из базы данных, используя больше поездок в базу данных.

Я написал пост в блоге выше, используя свой собственный опыт. Это здесь

Я надеюсь, что это поможет вам.

Да, это будет. Избегайте использования Включить, если оно расширяет несколько строк подробностей в строке основной таблицы.

Я считаю, что EF преобразует запрос в одно большое объединение вместо нескольких запросов. Таким образом, вы в конечном итоге продублируете данные своей основной таблицы в каждой строке таблицы сведений.

Например: Мастер -> Детали. Скажем, у мастера 100 строк, у деталей - 5000 строк (по 50 на каждого мастера).

Если вы лениво загружаете детали, вы возвращаете 100 строк (размер: мастер) + 5000 строк (размер: детали).

Если вы используете.Include("Детали"), вы возвращаете 5000 строк (размер: мастер + детали). По сути, основная часть дублируется более 50 раз.

Он умножается вверх, если вы включаете несколько таблиц.

Проверьте SQL, сгенерированный EF.

Я бы порекомендовал вам выполнить нагрузочные тесты и измерить производительность сайта в условиях стресса. Если вы выполняете сложные запросы по каждому запросу, вы можете рассмотреть возможность кэширования некоторых результатов.

Результат включения может измениться: он зависит от объекта, который вызывает метод включения.

Как пример, предложенный Ладиславом Мрнкой, предположим, что у нас есть сущность

Клиент (идентификатор, имя, адрес)

эта карта к этой таблице:

Id  |  Name   | Address
-----------------------
C1  |  Paul   |   XYZ   

и заказ объекта (Id, CustomerId, Total)

эта карта к этой таблице:

Id |  CustomerId  | Total
-----------------------
O1 |      C1      |  10.00
O2 |      C1      |  13.00

Отношение один клиент ко многим заказам


Пример 1: Клиент => Заказы

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == "C1");

Linq будет переведен в очень сложный SQL-запрос.

В этом случае запрос выдаст две записи и информация о клиенте будет реплицирована.

 Customer.Id   |   Customer.Name |    Order.Id |  Order.Total
-----------------------------------------------------------
     C1        |       Paul      |       O1    |    10.00     
     C1        |       Paul      |       O2    |    13.00   

Пример 2: Заказ => Клиент

var order = context.Orders
                      .Include("Customers")
                      .SingleOrDefault(c => c.Id == "O1");

Linq будет переведен в простом sql Join.

В этом случае запрос выдаст только одну запись без дублирования информации:

 Order.Id |  Order.Total |  Customer.Id   |   Customer.Name
-----------------------------------------------------------
     O1   |    10.00     |      C1        |       Paul    
Другие вопросы по тегам