Is this the right way of using ThenFetch() to load multiple collections? - nhibernate

I'm trying to load all the collections eagerly, using NHibernate 3 alpha 1. I'm wondering if this the right way of using ThenFetch()?
Properties with plural names are collections. The others are just a single object.
IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi).Fetch(mi => mi.Milestone)
.ThenFetch(m => m.PrimaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.SecondaryOwners)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Predecessors)
.Fetch(mi => mi.Milestone)
.ThenFetch(m => m.Function)
.Fetch(mi => mi.Milestone)
.ThenFetchMany(m => m.Jobs)
.ThenFetch(j => j.Source)
;
I thought of asking this in the NHibernate forums but unfortunately access to google groups is forbidden from where I am. I know Fabio is here, so maybe the guys from the NHibernate team can shed some light on this?
Thanks

Apparently, there's no "right" way to use ThenFetch in such a case. Your example works fine but SQL produced contains many joins to Milestone, which isn't that right.
Using IQueryOver instead of IQueryable allows you to use complex syntax in Fetch:
Fetch(p => p.B)
Fetch(p => p.B.C) // if B is not a collection ... or
Fetch(p => p.B[0].C) // if B is a collection ... or
Fetch(p => p.B.First().C) // if B is an IEnumerable (using .First() extension method)
So in your case it would be:
query // = session.QueryOver<X>()
.Fetch(mi => mi.Milestone).Eager
.Fetch(mi => mi.Milestone.PrimaryOwners).Eager
.Fetch(mi => mi.Milestone.SecondaryOwners).Eager
.Fetch(mi => mi.Milestone.Predecessors).Eager
.Fetch(mi => mi.Milestone.Function).Eager
.Fetch(mi => mi.Milestone.Jobs).Eager
.Fetch(mi => mi.Milestone.Jobs.First().Source).Eager

The one thing you are missing is that you should use FetchMany() and ThenFetchMany() is the child property is a collection.

IQueryable<T> milestoneInstances = Db.Find<T, IQueryable<T>>(db =>
from mi in db
where mi.RunDate == runDate
select mi);
var fetch = milestoneInstances.Fetch(f => f.Milestone);
fetch.ThenFetch(f => f.PrimaryOwners);
fetch.ThenFetch(f => f.SecondaryOwners);
//...

As leora said, make sure when fetching children collections that you use
FetchMany()
ThenFetchMany()
A new Fetch, should pick up from the root, but this does not always happen. Sometimes you need to create them as separate queries or use Criteria Futures to batch up a multiple fetch.

Related

Why do these two Fluent nHibernate queries produce different results?

I've been trying to work out why this query asking for a manager and his or her team only returns the first entry for the Team collection. Apparently, it's because I had FirstOrDefault at the end of the query. I was under the impression the FirstOrDefault would apply to the query as a whole, but it seems it's being applied to the Team collection as well.
Original query (shows only 1st member in Team):
session.Query<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team)
.Fetch(p => p.Manager)
.FirstOrDefault();
New query which returns full team:
session.Query<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team)
.Fetch(p => p.Manager)
.ToList().FirstOrDefault();
What would be the correct way to formulate this query? My need for a workaround implies I'm not doing this properly.
Background - mappings:
This is a basic hierarchical relationship with Manager being an IEmployee and Team being an IList of IEmployee.
References(x => x.Manager).Column("ManagerId");
HasMany(x => x.Team)
.AsList(index => index.Column("TeamIndex"))
.KeyColumn("ManagerId");
session.Query<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team)
.Fetch(p => p.Manager)
.FirstOrDefault();
In this query the FirstOrDefault works on the database as you expected.
session.Query<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team)
.Fetch(p => p.Manager)
.ToList().FirstOrDefault();
In this query the ToList works on the database. All behind works on the result of the ToList. So the FirstOrDefault gets the FirstOrDefault from the result set of the ToList. To get the same result you will need to add a order to your query. Sql does not grantee the same order of the result set when you do a select without order. The order in the result of ToList is different then the internal order in the first query.
I have been struggling with the same problem. For me, this occurred when upgrading from NH 3.1 -> 3.3. The problem is that with Linq, NHibernate 3.3 generates a SQL query that has a "Top(1)" statement in it, effectively killing the "Fetch"-part of the query. I solved it by switching from Linq to QueryOver. I believe this will work:
session.QueryOver<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team)
.Fetch(p => p.Manager)
.SingleOrDefault();
The problem is that I am asking for a Cartesian product (using Fetch) but also using FirstOrDefault; from the generated SQL I can see that that combination doesn't work, as you only get the first row of the Cartesian product (SQL generated: “FETCH NEXT 1 ROWS ONLY”).
I will need to write a different type of query if I want to do this, or else just use the ToList workaround which in this instance isn't doing much harm as I'm only expecting one result from the database anyway.
Example solution:
session.QueryOver<IEmployee>()
.Where(p => p.PersonalNumber == PersonalNumber)
.Fetch(p => p.Team).Eager
.Fetch(p => p.Manager).Eager
.SingleOrDefault();

Nhibernate queryover with multiple join fail

I have a nhibernate queryover like this:
var query = Session.QueryOver<Immobile>()
.WhereRestrictionOn(i => i.Agenzia.CodiceAgenzia).IsLike(codiceAgenzia)
.WhereRestrictionOn(i => i.StatoImmobile.StatoImmobileId).IsLike(statoId)
.And(i => i.Prezzo <= prezzo)
.And(i => i.Mq <= metriquadri);
The code compile but on execute I receive this exception:
could not resolve property: Agenzia.CodiceAgenzia of: Domain.Model.Immobile
What am I doing wrong?
QueryOver syntax doesnt work that way unfortunately on Referenced objects you need to join them first and then add the restriction..
Change the code to as follows:
Azengia azengiaAlias=null; //Azengia here is typeof(Immobile.Azengia) I am assuming it is Azengia
StatoImmobile statoImmobileAlias=null; //similarly StatoImmobile is assumed to be typeof(Immobile.StatoImmobile)
var query=Session.QueryOver<Immobile>()
.Where(i => i.Prezzo <= prezzo && i.Mq <= metriquadri)
.Inner.JoinAlias(x=>x.Agenzia,()=>azengiaAlias)
.Inner.JoinAlias(x=>x.StatoImmobile,()=.statoImmobileAlias)
.WhereRestrictionOn(() => azengiaAlias.CodiceAgenzia).IsLike(codiceAgenzia)
.WhereRestrictionOn(() => statoImmobileAlias.StatoImmobileId).IsLike(statoId);
Hope this helps.

NHibernate 3. Alternatives to "ThenFetch" in QueryOver

I'm using NHibernate 3.0 with both the LINQ provider and QueryOver. Sometimes I want to eager load related data, and there comes the method "Fetch" to the rescue, both in LINQ and QueryOver. Now I have a special scenario where I want to eager load a property not directly on the second level, like:
Foo f = ...;
f.A.B.C
with LINQ there's no problem, as you can "chain" fetches with the method "ThenFetch", like:
var result = Session.Query<Foo>().Fetch(a => a.A).ThenFetch(b => b.B).ThenFetch(c => c.C).ToList();
In QueryOver there's no such method, so how can I achieve the same result?
Thanks in advance.
I actually managed to solve this problem using two different approaches:
Approach one:
Session.QueryOver<Foo>().Fetch(x => x.A).Fetch(x => x.A.B).Fetch(x => x.A.B.C)
Approach two:
A a = null;
B b = null;
C c = null;
Session.QueryOver<Foo>()
.JoinAlias(x => x.A, () => a)
.JoinAlias(() => a.B, () => b)
.JoinAlias(() => b.C, () => c)
Both work (altough I'm not exactly sure if one of them generated "inner" and the other one "outer" joins).
Just as a curiosity, I'll post the reply they gave me on the NHibernate Jira:
query
.Fetch(p => p.B)
.Fetch(p => p.B.C) // if B is not a collection ... or
.Fetch(p => p.B[0].C) // if B is a collection ... or
.Fetch(p => p.B.First().C) // if B is an IEnumerable (using .First() extension method)
I think you can do that with JoinQueryOver
IQueryOver<Relation> actual =
CreateTestQueryOver<Relation>()
.Inner.JoinQueryOver(r => r.Related1)
.Left.JoinQueryOver(r => r.Related2)
.Right.JoinQueryOver(r => r.Related3)
.Full.JoinQueryOver(r => r.Related4)
.JoinQueryOver(r => r.Collection1, () => collection1Alias)
.Left.JoinQueryOver(r => r.Collection2, () => collection2Alias)
.Right.JoinQueryOver(r => r.Collection3)
.Full.JoinQueryOver(r => r.People, () => personAlias);

nhibernate queryover not loading eagerly with a many to many joinalias

I'm trying to eager load roles in many to many collection off of my User object.
Role role = null;
IQueryOver<User, User> query = session.QueryOver<User>()
.Fetch( p => p.Roles).Eager
.JoinAlias( q => q.Roles, () => role)
.Where(() => role.Active == true);
leaves me with user objects that have uninitialized roles members. If I remove the joinalias, they are initialized just fine. Is this just an NH3 bug or am I doing something wrong?
Another way to make eager load is to set LeftOuterJoin. It helped to us in a similar scenario
Role role = null;
IQueryOver<User, User> query = session.QueryOver<User>().Fetch( p => p.Roles).Eager
.JoinAlias( q => q.Roles, () => role, JoinType.LeftOuterJoin)
.Where(() => role.Active == true);
That's the expected behavior. If you use JoinAlias, you'll be filtering the collection elements, so it can't be initialized.
You need to use a subquery for filtering if you intend to use eager loading. on the same collection.

Duplicate Items Using Join in NHibernate Map

I am trying to retrieve the individual detail rows without having to create an object for the parent. I have a map which joins a parent table with the detail to achieve this:
Table("UdfTemplate");
Id(x => x.Id, "Template_Id");
Map(x => x.FieldCode, "Field_Code");
Map(x => x.ClientId, "Client_Id");
Join("UdfFields", join =>
{
join.KeyColumn("Template_Id");
join.Map(x => x.Name, "COLUMN_NAME");
join.Map(x => x.Label, "DISPLAY_NAME");
join.Map(x => x.IsRequired, "MANDATORY_FLAG")
.CustomType<YesNoType>();
join.Map(x => x.MaxLength, "DATA_LENGTH");
join.Map(x => x.Scale, "DATA_SCALE");
join.Map(x => x.Precision, "DATA_PRECISION");
join.Map(x => x.MinValue, "MIN_VALUE");
join.Map(x => x.MaxValue, "MAX_VALUE");
});
When I run the query in NH using:
Session.CreateCriteria(typeof(UserDefinedField))
.Add(Restrictions.Eq("FieldCode", code)).List<UserDefinedField>();
I get back the first row three times as opposed to the three individual rows it should return. Looking at the SQL trace in NH Profiler the query appears to be correct. The problem feels like it is in the mapping but I am unsure how to troubleshoot that process. I am about to turn on logging to see what I can find but I thought I would post here in case someone with experience mapping joins knows where I am going wrong.
Found aboutn about SetResults transformer here :
http://www.coderanch.com/t/216546/ORM/java/Hibernate-returns-duplicate-results
Which makes your code:
Session.CreateCriteria(typeof(UserDefinedField))
.Add(Restrictions.Eq("FieldCode", code))
.SetResultTransformer(CriteriaSpecification.DistinctRootEntity)
.List<UserDefinedField>();
Cheers
John