NHibernate Filtered Child Collection Lazy Loaded even with eager fetch specified

这一生的挚爱 提交于 2019-12-04 12:10:19
Radim Köhler

I would like to share my approach, maybe not the answer...

I. avoid fetching one-to-many (collections)

When creating any kind of complex queries (ICriteria, QueryOver) we should use (LEFT) JOIN only on a start schema. I.e. on many-to-one (References() in fluent). That leads to expected row count from the perspective of paging (there is always only ONE row per root Entity)

To avoid 1 + N issue with collections (but even with many-to-one in fact) we have the NHiberante powerful feature:

19.1.5. Using batch fetching

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)...

Read more here:

So, in our case, we would adjust mapping like this:

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. collection filtering

So, we managed to avoid 1 + N issue, and we also query only star schema. Now, how can we load just filtered set of items of our collection? Well, we have native and again very powerful NHibernate feature:

18.1. NHibernate filters.

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...

Read more about it here:

So in our case we would define filter

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

And we would need to extend our mapping again:

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

Now, before our query will be executed, we just have to enable that filter and provide proper ID of the year 2015:

// 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. Sub-query to filter root entity

Finally we can use sub-query, to restrict the amount of root entities to be returned with our query.

15.8. Detached queries and subqueries

Read more about it here:

so, our subquery could be

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

Summary: We can use lot of features, which are shipped with NHibernate. They are there for a reason. And with them together we can achieve stable and solid code, which is ready for further extending (paging at the first place)

NOTE: maybe I made some typos... but the overall idea should be clear

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!