Question

I've got a model where a Person has a lazy-loaded collection of Registrations. 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.

Était-ce utile?

La solution

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.

1. where clause

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

2. filter setting

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:

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top