NHibernate Filtered Child Collection Lazy Loaded,即使指定了eager fetch也是如此

我试图找出为什么没有过滤返回子集合,即使急切加载集合和生成的SQL是正确的。

这些课程的流利映射是:

public class OptionIdentifierMap : ClassMap { public OptionIdentifierMap() : base("OptionIdentifier") { //Id Mapping Removed HasMany(x => x.OptionPrices) .KeyColumn("OptionIdentifier_id") .Cascade.None(); } } public class OptionPriceMap : ClassMap { 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 { public PricingIncrementMap() : base("PricingIncrement") { Map(x => x.IncrementYear); HasMany(x => x.Options) .KeyColumn("PricingIncrement_id") .Cascade.None().Inverse(); } } 

实体是:

 public class PricingIncrement : Entity { public PricingIncrement() { Options = new List(); } public virtual int IncrementYear { get; set; } public virtual IList 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(); } public virtual IList OptionPrices { get; set; } } 

我试图查询具有特定PricingIncrement的optionprice值的所有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() .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())) .SetResultTransformer(CriteriaSpecification.DistinctRootEntity); 

在查看SQL事件探查器时,执行查询并且结果是正确的,我在OptionPrice表中为每个子项获取一行符合条件(在我的情况下为1),与可选的4行匹配OptionIdentifier(有4行)在PricingIncrement中的4和OptionPrice中的4,用于OptionIdentifier_id的每个PricingIncrement 7)

但是当我尝试迭代集合以获取某些值时,由于某种原因,nhibernate正在加载子集合,就像指定了延迟加载一样,并加载了完整的4个子行。 阅读文档FetchMode应该修复这个,防止nhibernate延迟加载子集合。 类似于N + 1常见问题。

我检查了SQL事件探查器以查看发生了什么,并且当我尝试访问时,nhibernate正在生成没有原始filter的查询来填充子集合。 如果我不访问该集合,则不会生成任何查询。

做一些测试我尝试了不同的连接类型和获取模式,到目前为止,迭代集合而没有hibernate加载所有元素的唯一方法是在连接类型LeftOuterJoin中指定,但这意味着不同的东西。

我试图搜索类似的问题,但所有人都说急切加载应该工作,或者提到我应该使用filter。 到目前为止我还没有找到任何答案。

任何帮助是极大的赞赏。

我想分享我的方法,也许不是答案……

I.避免取one-to-many (collections)

在创建任何类型的复杂查询(ICriteria,QueryOver)时,我们应该仅在启动模式上使用(LEFT) JOIN。 即many-to-one (流利的References() 。 这导致从分页的角度来看预期的行数(每个根实体总是只有一行)

为了避免集合的1 + N问题(但实际上甚至是多对一的)我们有NHiberante强大的function:

19.1.5。 使用批量提取

NHibernate可以有效地使用批量提取,也就是说,如果访问一个代理(或集合 ),NHibernate可以加载几个未初始化的代理。批量提取是惰性选择提取策略的优化

在这里阅读更多:

  • 如何在NHibernate中没有重复地加载加载关联?
  • 在使用Oracle时,如何使用Fluent NHibernate实现批量提取?

所以,在我们的例子中,我们会像这样调整映射:

 public PricingIncrementMap() : base("PricingIncrement") { Map(x => x.IncrementYear); HasMany(x => x.OptionPrices) .KeyColumn("OptionIdentifier_id") .Cascade.None() .Inverse() // I would use .Inverse() as well // batch fetching .BatchSize(100); } 

II。 集合过滤

因此,我们设法避免1 + N问题,我们也只查询星型模式。 现在,我们如何才能加载我们collections的过滤物品集? 好吧,我们有本机和非常强大的NHibernatefunction:

18.1。 NHibernatefilter。

NHibernate增加了预定义过滤条件的能力,并在类和集合级别附加这些filter。 过滤条件是能够定义一个与类和现有的“where”属性非常类似的限制子句以及各种集合元素……

在这里阅读更多相关信息:

  • 如何分配数据层级filter
  • 限制集合仅检索只读实体的最近条目

所以在我们的例子中我们将定义filter

 public class CollFilter : FilterDefinition { public CollFilter() { WithName("CollFilter") .WithCondition("PricingIncrement_id = :pricingIncrementId") .AddParameter("pricingIncrementId",NHibernate.Int32); } } 

我们需要再次扩展我们的映射:

 HasMany(x => x.OptionPrices) .KeyColumn("OptionIdentifier_id") .Cascade.None() .Inverse() // batch fetching .BatchSize(100) // this filter could be turned on later .ApplyFilter(); 

现在,在执行查询之前,我们只需要启用该filter并提供2015年的正确ID:

 // the ID of the PricingIncrement with year 2015 var pricingIncrementId thes.Session .QueryOver() .Where(x => x.IncrementYear == 2015) .Take(1) .Select(x => x.ID) .SingleOrDefault(); this.Session .EnableFilter("CollFilter") .SetParameter("pricingIncrementId", pricingIncrementId); // ... the star schema query could be executed here 

III。 用于过滤根实体的子查询

最后,我们可以使用子查询来限制使用我们的查询返回的根实体的数量。

15.8。 分离的查询和子查询

在这里阅读更多相关信息:

  • 查询HasMany引用
  • NHibernate Criteria list属性的任何元素都为true

所以,我们的子查询可能是

 // Subquery var subquery = DetachedCriteria.For() .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() .Add(Subqueries.PropertyIn("ID", subquery)) .SetResultTransformer(CriteriaSpecification.DistinctRootEntity); 

简介:我们可以使用NHibernate附带的许多function。 他们是有原因的。 通过它们,我们可以实现稳定可靠的代码,可以进一步扩展(首先是分页)

注意:也许我做了一些错别字……但总的想法应该是明确的