I've got a model where a Person
has a lazy-loaded collection of Registration
s. My NHibernate 3.3.3 query uses JoinQueryOver
across that relation and some more:
var reg = Session.QueryOver<Person>()
.Where(p => p.ID == pid)
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
.JoinQueryOver<Slot>(r => r.Slot)
.JoinQueryOver<Position>(s => s.Position)
.JoinQueryOver<AcademicTerm>(p => p.Term)
.Where(t => t.ID == currentTerm.ID)
.SingleOrDefault();
In the log, I see the expected SQL for the query, and I get the expected Person
instance, but when I access the Registrations collection, the full collection of registrations gets (lazy) loaded. I was expecting it would just be a partial collection of the registrations that meet the criteria.
In a previous question, I had the opposite problem, and fixed it by adding a Fetch()
clause to the query.
When does a JoinQueryOver criterion cause the root entity to have a partial collection, and when does it have an unloaded collection that will get lazy loaded? I feel like I should understand this.
My fluent mapping for Person
:
public class PersonMapping : ClassMap<Person>
{
public PersonMapping()
{
Id(x => x.ID).GeneratedBy.Guid();
Map(x => x.Username).Length(20).Unique();
Map(x => x.HashedPassword).Length(Security.HashedPasswordEncodedLength);
Map(x => x.PasswordSalt).Length(Security.PasswordSaltEncodedLength);
Map(x => x.LastName).Length(25).Index("FullName");
Map(x => x.FirstName).Length(20).Index("FullName");
Map(x => x.StudentID).Length(12).Nullable();
Map(x => x.EmployeeID).Length(12).Nullable();
Map(x => x.EmailAddress).Length(72);
Map(x => x.IsAdmin).Nullable();
Map(x => x.IsAgencyStaff).Nullable();
Map(x => x.IsCenterStaff).Nullable();
Map(x => x.IsFaculty).Nullable();
Map(x => x.IsRegistrant).Nullable();
Map(x => x.IsStudent).Nullable();
Map(x => x.Type);
Map(x => x.AuthenticationMode);
HasMany<Registration>(x => x.Registrations).KeyColumn("Registrant_id")
.LazyLoad()
.AsBag()
.Inverse()
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate();
}
}
Update
Comparing this with my other question (linked above), where I had the opposite problem, that one uses a Left.JoinQueryOver
to get from the base entity to the collection. I tried that here, and then got the expected partial collection. (I don't actually need the left join in the other case; I don't remember why it's there.)
The records fetched are the same with or without the left join, but NHibernate populates the collection in one case, and not in the other. I doubt that's behavior I can depend on, so I've changed my strategy to query for the registrations separately.
This condition in the QueryOver
:
...
.JoinQueryOver<Registration>(p => p.Registrations)
.Where(r => r.IsDropped == false)
...
Is in fact a WHERE clause for a Person. I mean, this condition is deciding if there is such a Person
, having at least one Registration
which is NOT dropped. It is not a way, how to filter the Registration colleciton. BUT:
NHibernate has two nice ways how to filter collections themselves. These are where clause and filter.
Easily said: we can extend the collection mapping, whit a SQL Statement, which will be always added as a where condition. See where 6.2. Mapping a Collection, extract:
where="arbitrary sql where condition"
== (optional) specify an arbitrary SQL WHERE condition to be used when retrieving or removing the collection (useful if the collection should contain only a subset of the available data)
so in our case the mapping:
HasMany<Registration>(
...
.Where("IsDropped = 0"); // SQL Statement
While where clause is static, baked into the assembly/mapping... filter is a way how to do that dynamically. So just imagine, that we would sometimes like to have a set of Registrations
, which are dropped, next time, which are not dropped.
We would like to be able to change "IsDropped = 0"
to "IsDropped = 1"
(better scenario could be switch by culture or lang). And that's where the filter could be applied, see 18.1. NHibernate filters
There is a small draft of the filter stuff with fluent, see the links below to get a complete picture.
HasMany
...
.ApplyFilter<MyFilter>()
And filter could be like:
public class MyFilter: FilterDefinition
{
public MyFilter()
{
WithName("dropped")
.WithCondition("IsDropped == :isDropped")
.AddParameter("isDropped", NHibernate.NHibernateUtil.Boolean);
}
}
And finally, we can enable the Filter for a whole session (coool, all collections will be filtered the same way in that transaction/session, all collection mapped with MyFilter
)
session.EnableFilter("dropped").SetParameter("isDropped", false);
See more here: