NHibernate непреднамеренная ленивая собственность loading - PullRequest
2 голосов
/ 02 июня 2010

Я представил сопоставление для бизнес-объекта, которое имеет (среди прочего) свойство с именем «Имя»:

public class Foo : BusinessObjectBase
{
    ...
    public virtual string Name { get; set; }
}

По какой-то причине, когда я выбираю объекты "Foo", NHibernate, кажется, применяет отложенную загрузку свойств (для простых свойств, а не ассоциаций):

Следующий фрагмент кода генерирует n + 1 операторов SQL, из которых первый выбирает только идентификаторы, а остальные n выбирают имя для каждой записи:

ISession session = ...IQuery query = session.CreateQuery(queryString);
ITransaction tx = session.BeginTransaction();

List<Foo> result = new List<Foo>();
foreach (Foo foo in query.Enumerable())
{
    result.Add(foo);
}

tx.Commit();
session.Close();

производит:

select foo0_.FOO_ID as col_0_0_ from V1_FOO foo0_
SELECT foo0_.FOO_ID as FOO1_2_0_, foo0_.NAME as NAME2_0_ FROM V1_FOO foo0_ 
    WHERE foo0_.FOO_ID=:p0;:p0 = 81
SELECT foo0_.FOO_ID as FOO1_2_0_, foo0_.NAME as NAME2_0_ FROM V1_FOO foo0_ 
    WHERE foo0_.FOO_ID=:p0;:p0 = 36470
SELECT foo0_.FOO_ID as FOO1_2_0_, foo0_.NAME as NAME2_0_ FROM V1_FOO foo0_ 
    WHERE foo0_.FOO_ID=:p0;:p0 = 36473

Аналогично, следующий код приводит к исключению LazyLoadingException после закрытия сеанса:

ISession session = ...
ITransaction tx = session.BeginTransaction();
Foo result = session.Load<Foo>(id);
tx.Commit();
session.Close();

Console.WriteLine(result.Name);

После этого сообщения , "lazy properties ... редко является важной функцией, которую можно включить ... (и) в Hibernate 3, по умолчанию отключено."

Так что я делаю не так? Мне удалось обойти LazyLoadingException, выполнив NHibernateUtil.Initialize(foo), но еще хуже то, что n + 1 SQL-операторов, которые приносят мои приложение на колени.

Вот как выглядит отображение:

<class name="Foo" table="V1_FOO">
    ...
    <property name="Name" column="NAME"/>
</class>

Кстати: абстрактный базовый класс BusinessObjectBase инкапсулирует свойство ID, которое служит внутренним идентификатором.

1 Ответ

6 голосов
/ 02 июня 2010

Не думаю, что это связано с отложенной загрузкой свойств. Это скорее из-за использования Enumerable и Load.

Ознакомьтесь с справочной документацией о Enumerable:

... Итератор будет загружать объекты на спрос, используя возвращенные идентификаторы по начальному запросу SQL (n + 1 выбирает всего) .

Либо использовать пакетную выборку, чтобы уменьшить количество запросов (в отображении класса)

<class name="Foo" table="V1_FOO" batch-size="20">

... или используйте List вместо Enumerable:

IQuery query = session.CreateQuery(queryString);
List<Foo> result query.List<Foo>();

Примечание: Enumerable имеет смысл, только если вы не ожидаете, что вам нужен весь результат, или в особых случаях, когда вы не хотите, чтобы они все были в памяти одновременно (тогда вам нужно Evict чтобы удалить их). В большинстве случаев List - это то, что вам нужно.


В случае Load создается только прокси (запрос не выполняется). При первом доступе к нему он загружается. (Это очень полезно, например, использовать этот прокси в качестве аргумента фильтра в запросах или связать его с другим объектом без необходимости загрузки его содержимого.) Если вам нужно его содержимое, используйте Get вместо

using (ISession session = ...)
using (ITransaction tx = session.BeginTransaction())
{
    Foo result = session.Get<Foo>(id);

    tx.Commit();
}
// could still fail in case of lazy loaded references
Console.WriteLine(result.Name);

... или, что еще лучше, используйте объект только во время открытого сеанса.

using (ISession session = ...)
using (ITransaction tx = session.BeginTransaction())
{
    Foo result = session.Load<Foo>(id);
    // should always work fine
    Console.WriteLine(result.Name);

    tx.Commit();
}
...