我为业务对象引入了一个映射,其中包含一个名为“Name”的属性:
public class Foo : BusinessObjectBase
{
...
public virtual string Name { get; set; }
}
出于某种原因,当我获取“Foo”对象时, NHibernate似乎应用了延迟属性加载(对于简单属性,而不是关联):
以下代码片段生成 n + 1 SQL语句,其中第一个只获取id,剩余的 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);
关注this post,“延迟属性...很少是启用...(和)在Hibernate 3中的重要功能,默认情况下处于禁用状态。”
那么我做错了什么?我设法通过执行NHibernateUtil.Initialize(foo)
来解决LazyLoadingException,但更糟糕的是 n + 1 sql使我的申请陷入困境的陈述。
这是映射的样子:
<class name="Foo" table="V1_FOO">
...
<property name="Name" column="NAME"/>
</class>
BTW:抽象的“BusinessObjectBase”基类封装了作为内部标识符的ID属性。
答案 0 :(得分:6)
我认为这不是由于懒惰的属性加载。这是因为使用了Enumerable
和Load
。
查看关于Enumerable
的{{3}}:
...迭代器将加载对象 需求,使用返回的标识符 通过初始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();
}