【问题标题】:NHibernate Filtered Child Collection Lazy Loaded even with eager fetch specifiedNHibernate Filtered Child Collection Lazy Loaded 即使指定了急切获取
【发布时间】:2015-06-09 22:35:23
【问题描述】:

我试图找出为什么即使在急切加载集合并且生成的 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 的 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 Profiler 时,查询执行且结果正确,我从与 OptionIdentifier 匹配的可用 4 行中为 OptionPrice 表中与条件匹配的每个子项获取一行,在我的情况下为在 PricingIncrement 中有 4 行,在 OptionPrice 中有 4 行,对于 OptionIdentifier_id 的每个 PricingIncrement 一个 7)

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

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

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

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

非常感谢任何帮助。

【问题讨论】:

    标签: c# nhibernate nhibernate-criteria


    【解决方案1】:

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

    我。避免获取 one-to-many (集合)

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

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

    19.1.5. Using batch fetching

    NHibernate 可以有效地利用批量获取,也就是说,如果访问一个代理,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);
    }
    

    二。集合过滤

    所以,我们设法避免了 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
    

    三。过滤根实体的子查询

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

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

    注意:也许我打错了……但总体思路应该很清楚

    【讨论】:

    • 这个想法很明确,我会试着看看它把我带到哪里,并发布我的测试结果。我会认为在一次数据库行程中急切加载子集合是一个更好的解决方案,而不是对单独的查询进行一个加载启动模式的一个加载子集合的查询。
    • 希望你能成功。你当然可以! ;) 我会说,NHibernate 需要更深入的了解才能使其成为真正强大的仆人。祝你好运,先生;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-24
    • 1970-01-01
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多