Im trying to find out why a child collection is being returned without filtering even when eager loading the collection and the generated SQL is correct.
The fluent mappings for the classes are:
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();
}
}
and 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();
}
}
And the Entities are:
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; }
}
Im trying to query All the OptionIdentifier that have an optionprice value for an specific PricingIncrement. The SQL Query that nhibernate generates from my criteria is:
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
The criteria I'm using to build this query is:
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);
When looking at SQL Profiler, the query executes and the result is correct, i get one row for each child in the OptionPrice table that matches the criteria, in my case one, from the available 4 rows that match the OptionIdentifier (there are 4 rows in PricingIncrement and 4 in OptionPrice one for each PricingIncrement for the OptionIdentifier_id 7)
But when i try to iterate the collection to get some values, for some reason nhibernate is loading the child collection, as if lazy load was specified, and loading the full 4 child rows. Reading the documentation FetchMode is supposed to fix this preventing nhibernate to lazy load child collections. Similar to a N+1 common issue.
I checked the SQL Profiler to see whats happening and nhibernate is generating queries without the original filter to fill the child collection when i try to access it. If i dont access the collection no query is generated.
Doing some testing i tried different join types and fetch modes, and so far the only way to iterate the collection without having hibernate load all the elements is to specify in the join type LeftOuterJoin, but this means something different.
I tried to search for issues similar but all of them say that eager loading should work, or mention that i should use filters. And so far i havent found any answer.
Any help is greatly appreciated.
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
Read more here:
So, in our case, we would adjust mapping like this:
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.
Read more about it here:
So in our case we would define filter
And we would need to extend our mapping again:
Now, before our query will be executed, we just have to enable that filter and provide proper ID of the year 2015:
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
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