Проблема производительности nHibernate при загрузке больших коллекций

(просто чтобы прояснить: мое приложение на самом деле не о сотрудниках и отделах. Я просто использую эти термины для примера).
У каждого отдела есть коллекция сотрудников, которая загружается лениво. Всякий раз, когда я добавляю нового сотрудника, я хочу убедиться, что он не существует в коллекции, поэтому я загружаю коллекцию в память и выполняю проверку.
Проблема в производственной среде, у меня есть несколько отделов с более чем 10000 сотрудников.
Я обнаружил, что получение коллекции, а затем сохранение нового сотрудника занимает много времени.
Я провел небольшой эксперимент, в котором скопировал точно такой же оператор выбора, сгенерированный nH, в ADO.Net SQLDataAdapter. Вот результаты:

***16:04:50:437*** DEBUG NHibernate.SQL - SELECT ... FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=@p0;@p0 = 2
***16:05:00:250*** DEBUG NHibernate.SQL - SELECT ... FROM dbo.TableD codeshared0_ left outer join dbo.[Department] department1_ on codeshared0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE codeshared0_.Employee_id in (select emp0_.Id FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=@p0);@p0 = 2
16:05:04:984 DEBUG NHibernate.SQL - Reading high value:select next_hi from dbo._uniqueKey with (updlock, rowlock)
16:05:05:078 DEBUG NHibernate.SQL - Updating high value:update dbo._uniqueKey set next_hi = @p0 where next_hi = @p1;@p0 = 10686, @p1 = 10685
***16:05:05:328*** DEBUG MyApp.Managers - commiting
16:05:12:000 DEBUG NHibernate.SQL - INSERT INTO dbo.[Employee] (...) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9);@p0 = 23/04/2011 04:04:49, @p1 = 23/04/2011 03:34:49, @p2 = 23/04/2011 04:04:49, @p3 = 23/04/2011 03:34:49, @p4 = '', @p5 = False, @p6 = 433, @p7 = NULL, @p8 = 2, @p9 = 10685
16:05:12:140 DEBUG NHibernate.SQL - UPDATE dbo.[Employee] SET Department_id = @p0 WHERE Id = @p1;@p0 = 2, @p1 = 10685
16:05:12:343 DEBUG MyApp.Managers - success
16:05:12:359 DEBUG MyApp.Tests - ------------------------------------------------------------
16:05:12:359 DEBUG MyApp.Tests - Finished nHib stuff- now switching to ADO 
16:05:12:359 DEBUG MyApp.Tests - starting SQL: SELECT ... FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=2
16:05:14:750 DEBUG MyApp.Tests - total rows received: 10036
16:05:14:750 DEBUG MyApp.Tests - SQL: SELECT ... FROM dbo.TableD codeshared0_ left outer join dbo.[Department] department1_ on codeshared0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE codeshared0_.Employee_id in (select emp0_.Id FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=2)
16:05:15:250 DEBUG MyApp.Tests - total rows received: 2421

Как вы можете видеть, выборка занимает ~15 секунд при нг, по сравнению с ~2 секундами в ADO.Net.
Из небольшого исследования я знаю, что nH, вероятно, не предназначен для хранения такого количества элементов в сеансе. Можете ли вы придумать какую-либо другую возможную причину этой проблемы или другое предложение, кроме фильтрации сотрудников на уровне БД?

Спасибо

-РЕДАКТИРОВАТЬ-
Следуя приведенным ниже советам, я попытался использовать Reflection Optimizer (без разницы) и IStatelessSession для загрузки моей коллекции (выдает исключение - коллекции не могут быть получены сессией без сохранения состояния).
Я думаю, что мой код в классе Департамента должен будет измениться с чистого:

if (this.Employees.Contains(emp))
{
  ...
}  

к этой "грязной" версии:

var employeesRepository = IOCContainer.Get<IEmployeesRepository>();  
if (employeesRepository.EmployeeExists(this,emp))
{
  ...
}  

У кого-нибудь есть лучшее предложение?

3 ответа

Решение

У вас нет причин загружать всех сотрудников в память. Вы должны написать запрос, используя HQL/Critiria API/Linq для NHibernate, чтобы проверить, существует ли уже сотрудник в БД. например:

var existingEmpoyee = session.Query<Employee>()
                             .Where(e => e.Equals(newEmployee))
                             .FirstOrDefault();
if(existingEmployee != null)
   // Insert new employee to DB

Я бы использовал StatelessSession и пакетную оптимизацию.

Сеанс будет отслеживать все загруженные объекты, и если мы загрузим много данных, он в конечном итоге вылетит с исключением из-за нехватки памяти. К счастью, NHibernate имеет готовое решение для этого сеанса без сохранения состояния. Код теперь выглядит так:

using (IStatelessSession s = sessionFactory.OpenStatelessSession())
{
    var books = new ActionableList<Book>(book => Console.WriteLine(book.Name));
    s.CreateQuery("from Book")
        .List(books);

}

Сеанс без сохранения состояния, в отличие от обычного сеанса NHibernate, не отслеживает загруженные объекты, поэтому код здесь и код считывателя данных по сути одно и то же.

Для пакетной оптимизации и многого другого: уловки производительности NHibernate.

Хммм. может быть, это слишком много, и на самом деле - я бы ожидал, что "lazy=extra" ISet будет вести себя так, но вы можете написать свой собственный "extra lazy" ISet. если вы не встретили лишнюю ленивую коллекцию - это коллекция, которая, например, когда вы запрашиваете ее счет, не получает все данные, а выдает запрос счета. Ваш дополнительный ленивый ISet может выдавать запрос о существовании, когда вы пытаетесь что-то добавить.

Если вы реализуете это, ваш код будет чистым, и вы сможете отправить код в ядро ​​nhibernate.

однако вы должны подумать о добавлении диапазона и быть осторожным, чтобы не выдавать N запросов

удачи.

Другие вопросы по тегам