Сколько Включить я могу использовать для 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