NHibernate过滤的子集合即使指定了急加载也是延迟加载的

3

我试图找出为什么在贪婪加载集合并且生成的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();
    }
}

和定价增量映射

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

我正在尝试查询特定定价增量的所有具有选项价格值的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 表中的每个子代都会得到一行,在我的情况下匹配的行数是1,因为有4行匹配了 OptionIdentifier(PricingIncrement 中有4行,OptionPrice 中也有4行,针对 Identifier_id 为7 的每个 PricingIncrement 有一个对应的 OptionPrice 行)。
但是当我尝试迭代集合以获取某些值时,出于某种原因 nhibernate 加载了子集合,就好像懒加载被指定一样,并加载了所有 4 个子行。阅读文档 FetchMode 应该可以解决这个问题,防止 nhibernate 懒加载子集合。类似于 N+1 常见问题。
我检查了 SQL Profiler 来查看发生了什么事情,当我尝试访问它时,nhibernate 生成没有原始过滤器的查询来填充子集合。如果我不访问集合,则不会生成任何查询。
进行一些测试后,我尝试了不同的连接类型和提取模式,到目前为止,唯一在不让 hibernate 加载所有元素的情况下迭代集合的方法是指定 LeftOuterJoin 的连接类型,但这意味着不同的东西。
我尝试搜索类似的问题,但它们都说急切加载应该起作用,或者提到我应该使用过滤器。到目前为止,我还没有找到任何答案。
非常感谢您的帮助。
1个回答

2
我想分享我的方法,或许不是答案...
第一点,避免获取“一对多”(集合)。在创建任何复杂查询(ICriteria,QueryOver)时,我们应该只在“起始”模式上使用(LEFT)JOIN。也就是在“多对一”(fluent中的References())上。这会导致从分页的角度获得预期的行数(每个根实体始终只有一个行)。
为了避免与集合(实际上包括多对一关系)相关的1 + N问题,我们可以使用NHiberante强大的功能:
19.1.5.使用批处理
NHibernate可以有效地使用批量获取,也就是说,NHibernate可以加载几个未初始化的代理,如果访问一个代理(或集合)。批量获取是延迟选择提取策略的优化......
在此处阅读更多:19.1.5. Using batch fetching

因此,在我们的情况下,我们将调整映射如下:

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

二、集合过滤

因此,我们成功避免了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. 分离查询和子查询

在此处阅读更多信息:

因此,我们的子查询可以是:

// 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一起提供的功能。它们存在是有原因的。通过它们,我们可以实现稳定和可靠的代码,这些代码可以进一步扩展(首先是分页)。
注意:也许我会打错字...但总体思路应该是清楚的。

这个想法很清晰,我会尝试看看它将引领我到哪里,并发布我的测试结果。我本以为,在一个数据库请求中急切加载子集合比进行两个分离的查询(一个用于加载起始模式,另一个用于加载子集合)是更好的解决方案。 - Ivan Fernandez
希望你能做到。当然你可以的! ;) 我想说,NHibernate需要更深入的理解才能使它成为一个真正强大的服务程序。祝你好运,先生 ;) - Radim Köhler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接