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

时间:2015-06-09 22:35:23

标签: c# nhibernate nhibernate-criteria

我试图找出为什么在没有过滤的情况下返回子集合,即使在急切加载集合并且生成的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 mapping

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的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<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事件探查器时,执行查询并且结果是正确的,我在OptionPrice表中为每个子项获取一行符合条件(在我的情况下为1),来自与OptionIdentifier匹配的可用4行(那里)在PricingIncrement中有4行,在OptionPrice中有4行,对于OptionIdentifier_id的每个PricingIncrement都有一行7)

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

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

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

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

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

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

予。避免提取 one-to-many (收藏)

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

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

<强> 19.1.5. Using batch fetching

  

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

在这里阅读更多内容:

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

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);
}

II。集合过滤

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

<强> 18.1. NHibernate filters.

  

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

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

因此,在我们的例子中,我们将定义过滤器

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

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

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

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附带的许多功能。他们是有原因的。通过它们,我们可以实现稳定可靠的代码,可以进一步扩展(首先是分页)

注意:也许我做了一些拼写错误......但总体思路应该是明确的