NHibernate Filtered Child Collection Lazy Loaded even with eager fetch specified
NHibernate Filtered Child Collection Lazy Loaded even with eager fetch specified
我试图找出为什么 child 集合在没有过滤的情况下返回,即使在急切加载集合并且生成的 SQL 是正确的。
类 的流畅映射是:
public class OptionIdentifierMap : ClassMap<OptionIdentifier>
{
public OptionIdentifierMap()
: base("OptionIdentifier")
{
//Id Mapping Removed
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None();
}
}
public class OptionPriceMap : ClassMap<OptionPrice>
{
public OptionPriceMap()
: base("OptionPrice")
{
//Id Mapping removed
References(x => x.Option)
.Column("OptionIdentifier_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_OptionIdentifier_id_OptionIdentifier_Id")
.Not.Nullable();
References(x => x.Increment)
.Column("PricingIncrement_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_PricingIncrement_id_PricingIncrement_Id")
.Not.Nullable();
Map(x => x.Price).Not.Nullable();
}
}
和 PricingIncrement 映射
public class PricingIncrementMap : ClassMap<PricingIncrement>
{
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.Options)
.KeyColumn("PricingIncrement_id")
.Cascade.None().Inverse();
}
}
实体是:
public class PricingIncrement : Entity
{
public PricingIncrement()
{
Options = new List<OptionPrice>();
}
public virtual int IncrementYear { get; set; }
public virtual IList<OptionPrice> Options { get; set; }
}
public class OptionPrice : Entity
{
public OptionPrice()
{
}
public virtual OptionIdentifier Option { get; set; }
public virtual PricingIncrement Increment { get; set; }
public virtual float Price { get; set; }
}
public class OptionIdentifier : Entity
{
public OptionIdentifier()
{
OptionPrices = new List<OptionPrice>();
}
public virtual IList<OptionPrice> OptionPrices { get; set; }
}
我正在尝试查询具有特定 PricingIncrement 的选项价格值的所有 OptionIdentifier。
nhibernate 根据我的标准生成的 SQL 查询是:
SELECT this_.Id as Id37_4_,
.......
FROM OptionIdentifier this_ inner join OptionPrice op2_ on this_.Id = op2_.OptionIdentifier_id
inner join PricingIncrement i3_ on op2_.PricingIncrement_id = i3_.Id
WHERE (this_.IsDeleted = 0)
and this_.Id in (7)
and i3_.IncrementYear = 2015
我用来构建此查询的条件是:
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.CreateAlias("OptionPrices", "op", JoinType.InnerJoin)
.CreateAlias("op.Increment", "i", JoinType.InnerJoin)
.SetFetchMode("op", FetchMode.Eager)
.SetFetchMode("i", FetchMode.Eager)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.Add(Expression.In("Id", idList.ToList<int>()))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
当查看 SQL Profiler 时,查询执行并且结果是正确的,我在 OptionPrice table 中为每个 child 获得一行符合条件,在我的第一种情况,从与 OptionIdentifier 匹配的可用 4 行中(PricingIncrement 中有 4 行,OptionPrice 中有 4 行,每个 PricingIncrement 对应 OptionIdentifier_id 7)
但是当我尝试迭代集合以获取一些值时,出于某种原因 nhibernate 正在加载 child 集合,就像指定了延迟加载一样,并加载了完整的 4 child 行.阅读文档 FetchMode 应该可以解决这个问题,防止 nhibernate 延迟加载 child 集合。类似于 N+1 常见问题。
我检查了 SQL Profiler 以查看发生了什么,当我尝试访问它时,nhibernate 正在生成没有原始过滤器的查询来填充 child 集合。如果我不访问该集合,则不会生成任何查询。
做一些测试我尝试了不同的连接类型和获取模式,到目前为止,在没有休眠加载所有元素的情况下迭代集合的唯一方法是在连接类型 LeftOuterJoin 中指定,但这意味着不同的东西。
我试图搜索类似的问题,但他们都说预加载应该有效,或者提到我应该使用过滤器。到目前为止我还没有找到任何答案。
非常感谢任何帮助。
我想分享我的方法,也许不是答案...
我。避免获取 one-to-many
(collections)
在创建任何类型的复杂查询(ICriteria、QueryOver)时,我们应该仅在 start 模式上使用 (LEFT) JOIN。 IE。在 many-to-one
(References()
流利)。从分页的角度来看,这会导致预期的行数(每个根实体始终只有一行)
为了避免 collections 的 1 + N 问题(但实际上 many-to-one) 我们有 NHiberante 强大的功能:
NHibernate can make efficient use of batch fetching, that is, NHibernate can load several uninitialized proxies if one proxy is accessed (or collections. Batch fetching is an optimization of the lazy select fetching strategy)...
在此处阅读更多内容:
- How to Eager Load Associations without duplication in NHibernate?
- How to implement batch fetching with Fluent NHibernate when working with Oracle?
所以,在我们的例子中,我们会像这样调整映射:
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse() // I would use .Inverse() as well
// batch fetching
.BatchSize(100);
}
二. collection过滤
因此,我们设法避免了 1 + N 问题,而且我们也只查询星型模式。现在,我们如何加载 collection 中刚刚过滤的项目集?好吧,我们有原生且非常强大的 NHibernate 功能:
NHibernate adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. A filter criteria is the ability to define a restriction clause very similiar to the existing "where" attribute available on the class and various collection elements...
在此处阅读更多相关信息:
- how to assign data layer-level filters
- Limit collection to retrieve only recent entries for readonly entity
所以在我们的例子中,我们将定义过滤器
public class CollFilter : FilterDefinition
{
public CollFilter()
{
WithName("CollFilter")
.WithCondition("PricingIncrement_id = :pricingIncrementId")
.AddParameter("pricingIncrementId",NHibernate.Int32);
}
}
而且我们需要再次扩展我们的映射:
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse()
// batch fetching
.BatchSize(100)
// this filter could be turned on later
.ApplyFilter<CollFilter>();
现在,在执行我们的查询之前,我们只需启用该过滤器并提供正确的 2015 年 ID:
// the ID of the PricingIncrement with year 2015
var pricingIncrementId thes.Session
.QueryOver<PricingIncrement>()
.Where(x => x.IncrementYear == 2015)
.Take(1)
.Select(x => x.ID)
.SingleOrDefault<int?>();
this.Session
.EnableFilter("CollFilter")
.SetParameter("pricingIncrementId", pricingIncrementId);
// ... the star schema query could be executed here
三。 Sub-query 过滤根实体
最后我们可以使用 sub-query 来限制我们的查询返回的根实体的数量。
15.8. Detached queries and subqueries
在此处阅读更多相关信息:
所以,我们的子查询可以是
// Subquery
var subquery = DetachedCriteria.For<OptionPrice >()
.CreateAlias("Increment", "i", JoinType.InnerJoin)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.SetProjection(Projections.Property("Option.ID"));
// root query, ready for paging, and still filtered as wanted
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.Add(Subqueries.PropertyIn("ID", subquery))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
总结:我们可以使用很多 NHibernate 附带的特性。他们在那里是有原因的。与他们一起我们可以实现稳定可靠的代码,为进一步扩展做好准备(首先分页)
注意:可能我打错了...但总体思路应该很清楚
我试图找出为什么 child 集合在没有过滤的情况下返回,即使在急切加载集合并且生成的 SQL 是正确的。
类 的流畅映射是:
public class OptionIdentifierMap : ClassMap<OptionIdentifier>
{
public OptionIdentifierMap()
: base("OptionIdentifier")
{
//Id Mapping Removed
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None();
}
}
public class OptionPriceMap : ClassMap<OptionPrice>
{
public OptionPriceMap()
: base("OptionPrice")
{
//Id Mapping removed
References(x => x.Option)
.Column("OptionIdentifier_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_OptionIdentifier_id_OptionIdentifier_Id")
.Not.Nullable();
References(x => x.Increment)
.Column("PricingIncrement_id")
.Cascade.None()
.ForeignKey("FK_OptionPrice_PricingIncrement_id_PricingIncrement_Id")
.Not.Nullable();
Map(x => x.Price).Not.Nullable();
}
}
和 PricingIncrement 映射
public class PricingIncrementMap : ClassMap<PricingIncrement>
{
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.Options)
.KeyColumn("PricingIncrement_id")
.Cascade.None().Inverse();
}
}
实体是:
public class PricingIncrement : Entity
{
public PricingIncrement()
{
Options = new List<OptionPrice>();
}
public virtual int IncrementYear { get; set; }
public virtual IList<OptionPrice> Options { get; set; }
}
public class OptionPrice : Entity
{
public OptionPrice()
{
}
public virtual OptionIdentifier Option { get; set; }
public virtual PricingIncrement Increment { get; set; }
public virtual float Price { get; set; }
}
public class OptionIdentifier : Entity
{
public OptionIdentifier()
{
OptionPrices = new List<OptionPrice>();
}
public virtual IList<OptionPrice> OptionPrices { get; set; }
}
我正在尝试查询具有特定 PricingIncrement 的选项价格值的所有 OptionIdentifier。 nhibernate 根据我的标准生成的 SQL 查询是:
SELECT this_.Id as Id37_4_,
.......
FROM OptionIdentifier this_ inner join OptionPrice op2_ on this_.Id = op2_.OptionIdentifier_id
inner join PricingIncrement i3_ on op2_.PricingIncrement_id = i3_.Id
WHERE (this_.IsDeleted = 0)
and this_.Id in (7)
and i3_.IncrementYear = 2015
我用来构建此查询的条件是:
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.CreateAlias("OptionPrices", "op", JoinType.InnerJoin)
.CreateAlias("op.Increment", "i", JoinType.InnerJoin)
.SetFetchMode("op", FetchMode.Eager)
.SetFetchMode("i", FetchMode.Eager)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.Add(Expression.In("Id", idList.ToList<int>()))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
当查看 SQL Profiler 时,查询执行并且结果是正确的,我在 OptionPrice table 中为每个 child 获得一行符合条件,在我的第一种情况,从与 OptionIdentifier 匹配的可用 4 行中(PricingIncrement 中有 4 行,OptionPrice 中有 4 行,每个 PricingIncrement 对应 OptionIdentifier_id 7)
但是当我尝试迭代集合以获取一些值时,出于某种原因 nhibernate 正在加载 child 集合,就像指定了延迟加载一样,并加载了完整的 4 child 行.阅读文档 FetchMode 应该可以解决这个问题,防止 nhibernate 延迟加载 child 集合。类似于 N+1 常见问题。
我检查了 SQL Profiler 以查看发生了什么,当我尝试访问它时,nhibernate 正在生成没有原始过滤器的查询来填充 child 集合。如果我不访问该集合,则不会生成任何查询。
做一些测试我尝试了不同的连接类型和获取模式,到目前为止,在没有休眠加载所有元素的情况下迭代集合的唯一方法是在连接类型 LeftOuterJoin 中指定,但这意味着不同的东西。
我试图搜索类似的问题,但他们都说预加载应该有效,或者提到我应该使用过滤器。到目前为止我还没有找到任何答案。
非常感谢任何帮助。
我想分享我的方法,也许不是答案...
我。避免获取 one-to-many
(collections)
在创建任何类型的复杂查询(ICriteria、QueryOver)时,我们应该仅在 start 模式上使用 (LEFT) JOIN。 IE。在 many-to-one
(References()
流利)。从分页的角度来看,这会导致预期的行数(每个根实体始终只有一行)
为了避免 collections 的 1 + N 问题(但实际上 many-to-one) 我们有 NHiberante 强大的功能:
NHibernate can make efficient use of batch fetching, that is, NHibernate can load several uninitialized proxies if one proxy is accessed (or collections. Batch fetching is an optimization of the lazy select fetching strategy)...
在此处阅读更多内容:
- How to Eager Load Associations without duplication in NHibernate?
- How to implement batch fetching with Fluent NHibernate when working with Oracle?
所以,在我们的例子中,我们会像这样调整映射:
public PricingIncrementMap()
: base("PricingIncrement")
{
Map(x => x.IncrementYear);
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse() // I would use .Inverse() as well
// batch fetching
.BatchSize(100);
}
二. collection过滤
因此,我们设法避免了 1 + N 问题,而且我们也只查询星型模式。现在,我们如何加载 collection 中刚刚过滤的项目集?好吧,我们有原生且非常强大的 NHibernate 功能:
NHibernate adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. A filter criteria is the ability to define a restriction clause very similiar to the existing "where" attribute available on the class and various collection elements...
在此处阅读更多相关信息:
- how to assign data layer-level filters
- Limit collection to retrieve only recent entries for readonly entity
所以在我们的例子中,我们将定义过滤器
public class CollFilter : FilterDefinition
{
public CollFilter()
{
WithName("CollFilter")
.WithCondition("PricingIncrement_id = :pricingIncrementId")
.AddParameter("pricingIncrementId",NHibernate.Int32);
}
}
而且我们需要再次扩展我们的映射:
HasMany<OptionPrice>(x => x.OptionPrices)
.KeyColumn("OptionIdentifier_id")
.Cascade.None()
.Inverse()
// batch fetching
.BatchSize(100)
// this filter could be turned on later
.ApplyFilter<CollFilter>();
现在,在执行我们的查询之前,我们只需启用该过滤器并提供正确的 2015 年 ID:
// the ID of the PricingIncrement with year 2015
var pricingIncrementId thes.Session
.QueryOver<PricingIncrement>()
.Where(x => x.IncrementYear == 2015)
.Take(1)
.Select(x => x.ID)
.SingleOrDefault<int?>();
this.Session
.EnableFilter("CollFilter")
.SetParameter("pricingIncrementId", pricingIncrementId);
// ... the star schema query could be executed here
三。 Sub-query 过滤根实体
最后我们可以使用 sub-query 来限制我们的查询返回的根实体的数量。
15.8. Detached queries and subqueries
在此处阅读更多相关信息:
所以,我们的子查询可以是
// Subquery
var subquery = DetachedCriteria.For<OptionPrice >()
.CreateAlias("Increment", "i", JoinType.InnerJoin)
.Add(Restrictions.Eq("i.IncrementYear", 2015))
.SetProjection(Projections.Property("Option.ID"));
// root query, ready for paging, and still filtered as wanted
ICriteria pagedCriteria = this.Session.CreateCriteria<OptionIdentifier>()
.Add(Subqueries.PropertyIn("ID", subquery))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity);
总结:我们可以使用很多 NHibernate 附带的特性。他们在那里是有原因的。与他们一起我们可以实现稳定可靠的代码,为进一步扩展做好准备(首先分页)
注意:可能我打错了...但总体思路应该很清楚