Nhibernate QueryOver - why do I have to specify JoinQueryOver - nhibernate

In my NHibernate mappings I have two objects- Listing and User. One user can have many listings, and the (Fluent) mappings are set up as such:
Listing:
References<User>(h => h.User).ForeignKey("fk_UserID").Not.LazyLoad().Fetch.Join().Cascade.SaveUpdate();
User:
HasMany<Listing>(u => u.Listings);
This works fine. However, when I started playing around with QueryOver, I tried:
DbSession.QueryOver<HaveListing>()
.Where(h => h.IsModerated == false)
.And(h => h.User.SpammedStatus == false)
Which fails. This, however, works:
DbSession.QueryOver<HaveListing>()
.Where(h => h.IsModerated == false)
.JoinQueryOver(h => h.User)
.Where(u => u.SpammedStatus == false)
Obviously, using the latter is fine, but I wanted to make sure that I'm not missing something- my relationships are defined in the mappings, so do I really need to specify the join each time in order to do a WHERE on User? It would be a waste to include these joins every time when it isn't necessary.

QueryOver is not LINQ. It uses Expressions to specify property names, but under the hood it's Criteria, so it's bound to the same rules (all joins are explicit)
Unless you have a compelling reason not to, try the following instead:
DbSession.Query<HaveListing>()
.Where(h => h.IsModerated == false &&
h.User.SpammedStatus == false)

Related

NHibernate query with FetchMany returns incorrect results

I have two related entities: Job and Group, with a Many-To-Many relationship.
I am performing a simple query to retrieve a specific Job and it's associated Group (through the GroupRecipients property):
var job = jobsRepo.Get()
.Where(j => j.Id == jobKey.Id)
.FirstOrDefault();
var countA = job.GroupRecipients.Count;
The result of which is countA==2, which corresponds to the state in the database.
The first weirdness occurs when I add a FetchMany:
var job = jobsRepo.Get()
.FetchMany(x => x.GroupRecipients)
.Where(j => j.Id == jobKey.Id)
.FirstOrDefault();
var countB = job.GroupRecipients.Count;
This results in countB==1. Only one item appears in the job.GroupRecipients collection, which contradicts the state in the database.
But it get's even more interesting. If I run the following in succession:
var job = jobsRepo.Get()
.Where(j => j.Id == jobKey.Id)
.FirstOrDefault();
var countA = job.GroupRecipients.Count;
var jobB = jobsRepo.Get()
.FetchMany(x => x.GroupRecipients)
.Where(j => j.Id == jobKey.Id)
.FirstOrDefault();
var countB = jobB.GroupRecipients.Count;
Then I get countB==2, the expected result. Removing the line with countA again causes countB==1.
Some more info:
I perform the queries in a transaction in a stateless session.
The version of NHibernate is 3.3.1
The two issues can thus be summarized as following:
FetchMany returns partial results
One query is dependent on another query in an unexpected way.
Any explanation of this behavior is very welcome.
This appears to be a bug or mismatch in the way the LINQ provider handles FirstOrDefault - on mine, it appears to add a blanket LIMIT 1 to the query, which obviously doesn't work with the eager loading (which results in an OUTER JOIN).
I've solved it by explicitly converting to an enumerable in the middle:
var job = jobsRepo.Get()
.FetchMany(x => x.GroupRecipients)
.AsEnumerable()
.FirstOrDefault(j => j.Id == jobKey.Id);
Which is a bit of a hack, but solves the problem.
Likely, your second example works because the first loads the entity into the cache, and so nHibernate doesn't feel the need to go to the database (and encounter the faulty logic in the LINQ provider).

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, Projections and Aliases

I have an nhibernate issue where I am projecting the sql Coalesce function.
I am comparing two string properties having the same name, from two different entities. In the resulting sql, the same property from only the first entity is being compared thus:
var list = Projections.ProjectionList();
list.Add(
Projections.SqlFunction("Coalesce",
NHibernateUtil.String,
Projections.Property<TranslatedText>(tt => tt.ItemText),
Projections.Property<TextItem>(ti => ti.ItemText)));
var q = Session.QueryOver<TextItem>()
.Left.JoinQueryOver(ti => ti.TranslatedItems);
Evaluating q results in this sql
coalesce(this_.ItemText, this_.ItemText)
the this_ in the RHS needs to be an aliased table
I can use Projections.Alias(Projections.Property<TranslatedText>(tt => tt.ItemText), "ttAlias") but am not sure how to map "ttAlias" in the JoinQueryOver.
I can create an alias there too, but can't see how to name it.
TranslatedText ttAlias = null;
...
JoinQueryOver(ti => ti.TranslatedItems, () => ttAlias)
Aliases are variables in QueryOver, like you showed in the JoinQueryOver call. Alias names (strings) should not be needed in QueryOver, they are for Criteria queries.
To the problem itself: I can't test it right now, but I think this should work:
Projections.Property(() => ttAlias.ItemText)
I used this topic as a resource while writing a unit test. This QueryOver works well and may help others with similar issues. QueryOver still struggles with property mapping to transformers using expressions. It's technically possible to remove "Id" but IMHO it hinders clarity.
The complete example is on GitHub
String LocalizedName = "LocalizedName";
//Build a set of columns with a coalese
ProjectionList plColumns = Projections.ProjectionList();
plColumns.Add(Projections.Property<Entity>(x => x.Id), "Id");
plColumns.Add(Projections.SqlFunction("coalesce",
NHibernateUtil.String,
Projections.Property<Entity>(x => x.EnglishName),
Projections.Property<Entity>(x => x.GermanName))
.WithAlias(() => LocalizedName));
ProjectionList plDistinct = Projections.ProjectionList();
plDistinct.Add(Projections.Distinct(plColumns));
//Make sure we parse and run without error
Assert.DoesNotThrow(() => session.QueryOver<Entity>()
.Select(plDistinct)
.TransformUsing(Transformers.AliasToBean<LocalizedEntity>())
.OrderByAlias(() => LocalizedName).Asc
.Skip(10)
.Take(20)
.List<LocalizedEntity>());

NHibernate Multiquery for eager loading without joins

Is it possible to use a multiquery and have two hql queries returning two different sets of entities where one of the sets are used in the other and that the session "fixes" this via the first level cache?
E.g. scenario (a dumb one and it could be solved with joins)
public class Room
{
...
public virtual ISet<Bookings> Bookings {get;set;}
public virtual bool IsAvailible {get;set;}
...
}
public class Booking
{
...
}
After executing a multicriteria with two hql's:
returning all rooms where
IsAvailible = true
returning all bookings having a room that has a room that IsAvailible
when accessing a room from the result and its bookings I want them to be resolved from the second resultset via the firstlevel cache of the session and there by avoiding n+1.
Generally speaking, NHibernate can use the cache to "combine" the results from queries executed through Multiquery. However, it should be noted that this usually only applies to cases where lazy collections are loaded with no restrictions whatsoever.
Examples:
Invoice iAlias = null;
InvoiceDetails idAlias = null;
// Base-Query: get Invoices with certain condition
var invoices = session.QueryOver<Invoice>()
.Where(i => i.Number == "001")
.Future<Invoice>();
// Option 1: this will still cause N+1 if we iterate through invoices,
// because it doesn't know better
var invoicedetails = session.QueryOver<InvoiceDetails>()
.JoinAlias(a => a.Invoice, () => iAlias)
.Where(() => iAlias.Number == "001")
.Future<InvoiceDetails>();
// Option 2: this will still cause N+1 if we iterate through invoices,
// because we limited the possible results using a where-condition
var invoices2 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.And(() => idAlias.Quantity > 5)
.Future<Invoice>();
// Option 3: this will work without N+1, because we don't use a filter
// -> NHibernate will use the collection in cache
var invoices3 = session.QueryOver<Invoice>()
.Left.JoinAlias(i => i.Details, () => idAlias)
.Where(i => i.Number == "001")
.Future<Invoice>();
foreach (Invoice i in invoices)
{
int count = i.Details.Count;
}
If we comment out two of the three options and execute the code, we will see that only option 3 will prevent a N+1, the other two will still load the InvoiceDetails for each Invoice in the loop.
Of course this is a very simple example and it is obvious that Option 3 could also be executed without the Base-query and still return the same result, but I hope you get the idea.
In the case where we load two different sets of entities, i.e. the root class is different as in Option 1, this "combining" will most likely not work.
Sorry, if I used QueryOver instead of HQL, but the same rules apply.
Gyus, keep in mind that sometimes you can have similar problems because of
LeftOuterJoin is not set.
.JoinAlias(x => x.Prop, () => propAlias, JoinType.LeftOuterJoin)

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.