Выберите N + 1 в следующей структуре Entity Framework

Одна из немногих действительных жалоб, которые я слышу о EF4 по отношению к NHibernate, заключается в том, что EF4 плохо справляется с лениво загруженными коллекциями. Например, в лениво загруженной коллекции, если я скажу:

if (MyAccount.Orders.Count() > 0) ;

EF вытащит всю коллекцию (если это еще не так), в то время как NH будет достаточно умным, чтобы выпустить select count(*)

У NH также есть хорошая выборка пакетов, чтобы помочь с проблемой select n + 1. Насколько я понимаю, ближайший EF4 может прийти к этому с помощью метода Include.

Может ли команда EF пропустить какие-либо указания, что это будет исправлено в следующей итерации? Я знаю, что они усердно работали на POCO, но это, кажется, что это будет популярным исправить.

Ответ 1

То, что вы описываете, не является проблемой N + 1. Примером проблемы с N + 1 является здесь. N + 1 означает, что вы выполняете N + 1, а не один (или два). В вашем примере это, скорее всего, означает:

// Lazy loads all N Orders in single select
foreach(var order in MyAccount.Orders)
{
  // Lazy loads all Items for single order => executed N times
  foreach(var orderItem in order.Items)
  {
     ...
  }
}

Это легко решить:

// Eager load all Orders and their items in single query
foreach(var order in context.Accounts.Include("Orders.Items").Where(...))
{
 ...
}

Ваш пример выглядит верным для меня. У вас есть коллекция, которая предоставляет IEnumerable, и вы выполняете на ней операцию Count. Коллекция ленив загружена, и подсчет выполняется в памяти. Возможность перевода запроса Linq к SQL доступна только на IQueryable с деревьями выражений, представляющими запрос. Но IQueryable представляет запрос = каждый доступ означает новое выполнение в БД, так, например, проверка цикла Count в цикле будет выполнять запрос БД на каждой итерации.

Так что это больше о реализации динамического прокси.


Подсчет связанных объектов без их загрузки уже возможен в CTP5 первого кода (окончательная версия будет называться EF 4.1) при использовании DbContext вместо ObjectContext, но не путем прямого взаимодействия с коллекцией. Вам нужно будет использовать что-то вроде:

int count = context.Entry(myAccount).Collection(a => a.Orders).Query().Count();

Query метод возвращает готовый IQueryable, который, вероятно, работает EF, если вы используете ленивую загрузку, но можете изменить запрос - здесь я использовал Count.