Метод First()
переводится в SQL (по крайней мере, T-SQL) как SELECT TOP 1 ...
. В сочетании с выбором соединения это вернет одну строку, содержащую одного клиента, один заказ для этого клиента и одну позицию для заказа. Вы можете счесть это ошибкой в Linq2NHibernate, но поскольку выборка из соединения встречается редко (и я думаю, что на самом деле вы снижаете свою производительность, используя одинаковые значения полей Customer и Order в сети как часть строки для каждого элемента), я сомневаюсь в команде это исправит.
То, что вы хотите - это один клиент, затем все заказы для этого клиента и все товары для всех этих заказов. Это происходит, когда NHibernate запускает SQL, который извлекает одну полную запись Customer (которая будет строкой для каждой строки заказа) и создает граф объекта Customer. Превращение перечислимого в список и получение первого элемента работает, но следующее будет немного быстрее:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();
функция AsEnumerable () форсирует оценку IQueryable, созданного Query и модифицированного другими методами, выплевывая Enumerable в память, не перетаскивая его в конкретный список (NHibernate может, если пожелает, просто получить достаточно информации из DataReader для создания одного полного экземпляра верхнего уровня). Теперь метод First () больше не применяется к IQueryable для преобразования в SQL, но вместо этого он применяется к перечисляемому в памяти объектному графу, который после NHibernate выполнил свою задачу и с учетом предложения Where, должно быть ноль или одна запись клиента с гидратированной коллекцией заказов.
Как я уже сказал, я думаю, что вы наносите себе вред, используя выборку из соединений. Каждая строка содержит данные для Клиента и данные для Заказа, соединенные с каждой отдельной строкой. Это много избыточных данных, которые, я думаю, обойдутся вам дороже, чем даже стратегия запросов N + 1.
Лучший способ справиться с этим - один запрос на объект для извлечения потомков этого объекта. Это будет выглядеть так:
var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();
customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();
foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();
Для этого требуется запрос для каждого Заказа плюс два на уровне Клиента, и он не будет возвращать повторяющихся данных. Это будет работать намного лучше, чем отдельный запрос, возвращающий строку, содержащую каждое поле Заказчика и Заказа вместе с каждым Элементом, а также лучше, чем отправка запроса по Элементу плюс запрос по Заказу плюс запрос для Заказчика.