Я использую Entity Framework в приложении C # и использую отложенную загрузку. При вычислении суммы свойства в коллекции элементов возникают проблемы с производительностью. Позвольте мне проиллюстрировать это упрощенной версией моего кода:
public decimal GetPortfolioValue(Guid portfolioId) {
var portfolio = DbContext.Portfolios.FirstOrDefault( x => x.Id.Equals( portfolioId ) );
if (portfolio == null) return 0m;
return portfolio.Items
.Where( i =>
i.Status == ItemStatus.Listed
&&
_activateStatuses.Contains( i.Category.Status )
)
.Sum( i => i.Amount );
}
Поэтому я хочу получить значение для всех моих элементов, которые имеют определенный статус, родительский статус которого также имеет определенный статус.
При регистрации запросов, сгенерированных EF, я вижу, что он сначала выбирает мой Portfolio
(что нормально). Затем он выполняет запрос для загрузки всех Item
сущностей, которые являются частью этого портфеля. И затем он начинает выбирать ВСЕ Category
сущностей для каждого Item
по одному. Поэтому, если у меня есть портфель, содержащий 100 элементов (каждый со своей категорией), он буквально выполняет 100 SELECT ... FROM categories WHERE id = ...
запросов.
Так что, похоже, он просто извлекает всю информацию, сохраняет ее в своей памяти и затем вычисляетсумма. Почему он не выполняет простое соединение между моими таблицами и вычисляет его следующим образом?
Вместо выполнения 102 запросов для вычисления суммы из 100 элементов, я ожидал бы что-то вроде:
SELECT
i.id, i.amount
FROM
items i
INNER JOIN categories c ON c.id = i.category_id
WHERE
i.portfolio_id = @portfolioId
AND
i.status = 'listed'
AND
c.status IN ('active', 'pending', ...);
, по которому он мог бы затем рассчитать сумму (если он не может использовать SUM непосредственно в запросе).
В чем проблема и как я могу улучшить производительность, кроме написания чистого ADOзапрос вместо использования Entity Framework?
Чтобы завершить, вот мои сущности EF:
public class ItemConfiguration : EntityTypeConfiguration<Item> {
ToTable("items");
...
HasRequired(p => p.Portfolio);
}
public class CategoryConfiguration : EntityTypeConfiguration<Category> {
ToTable("categories");
...
HasMany(c => c.Products).WithRequired(p => p.Category);
}
РЕДАКТИРОВАТЬ на основе комментариев:
Я не сделалдумаю, что это важно, но _activeStatuses
- это список перечислений.
private CategoryStatus[] _activeStatuses = new[] { CategoryStatus.Active, ... };
Но, вероятно, более важно то, что я не учел, что статус в базе данных является строкой ("active "," pending ", ...) но я сопоставляю их с перечислением, используемым в приложении. И поэтому EF не может это оценить? Фактический код:
... && _activateStatuses.Contains(CategoryStatusMapper.MapToEnum(i.Category.Status)) ...
EDIT2
Действительно, отображение является большой частью проблемы, но сам запрос кажется самой большой проблемой. Почему разница в производительности между этими двумя запросами так велика?
// Slow query
var portfolio = DbContext.Portfolios.FirstOrDefault(p => p.Id.Equals(portfolioId));
var value = portfolio.Items.Where(i => i.Status == ItemStatusConstants.Listed &&
_activeStatuses.Contains(i.Category.Status))
.Select(i => i.Amount).Sum();
// Fast query
var value = DbContext.Portfolios.Where(p => p.Id.Equals(portfolioId))
.SelectMany(p => p.Items.Where(i =>
i.Status == ItemStatusConstants.Listed &&
_activeStatuses.Contains(i.Category.Status)))
.Select(i => i.Amount).Sum();
Первый запрос выполняет МНОГО маленьких SQL-запросов, а второй просто объединяет все в один более крупный запрос. Я ожидаю, что даже первый запрос запустит один запрос, чтобы получить значение портфеля.