Reputation: 553
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");
Upvotes: 1
Views: 1180
Reputation: 608
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();
Upvotes: 2
Reputation: 553
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();
Upvotes: 1
Reputation: 27944
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.
Upvotes: 3