NHibernate eager load with FetchMany for subclass only - nhibernate

I have a following (simplified) hierarchy:
class Account
{
AccountType Type;
long Id;
string Name;
}
public enum AccountType
{
UsualAccount = 0,
SpecialAccount = 1
}
class SpecialAccount : Account
{
List<SpecialItem> SpecialItems;
}
class SpecialItem
{
long Id;
string Name;
}
To help NHibernate to detect subclass SpecialItem, I use the following code in the Account mapping:
<discriminator column="Type" formula="Type"/>
and SpecialItems have lazy="false" set in the mapping, but as far as I know, it is ignored during LINQ queries.
Now when I use LINQ and call
Session.Query<Account>().ToList();
I see that SpecialItems are fetched in a separate query. I would like to load them eagerly.
I could do that using
Session.Query<Account>().FetchMany(a => a.SpecialItems).ToList();
but there is a problem - SpecialItems is the property of the SpecialAccount. So I somehow need FetchMany to work only if a is a SpecialAccount class.
I tried even something as ugly as:
Session.Query<Account>().
FetchMany(a => (a is SpecialAccount ? (a as SpecialAccount ).SpecialItems : null));
but NHibernate still selected SpecialItems in a separate query.
How can I make NHibernate select SpecialItems for SpecialAccount in a single query?
Additional information
I am using MS SQL Express 2012 and NHibernate configuration has the following lines:
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="adonet.batch_size">50</property>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
NHibernate.dll version 3.2.0.4000
Here are the queries generated by NHibernate for one existing SpecialAccount with Id=2, as they appear in MS SQL Profiler (with selected events RPCCompleted, Exec Prepared SQL, SQL:StmtCompleted):
select account0_.Id as Id5_, account0_.Type as Type5_, account0_.Name as Name5_, account0_.Type as clazz_ from tAccount account0_
exec sp_executesql N'SELECT specialite0_.AccountId as Id1_, specialite0_.Id as specialite1_1_, specialite0_.Id as specialite1_13_0_, specialite0_.Name as Name13_0_
FROM tSpecialItem specialite0_ WHERE specialite0_.Id=#p0',N'#p0 bigint',#p0=2

You can't to a "conditional fetch". You need at least two queries.
You can, however, group them using a Future LINQ query, so it would be effectively one roundtrip:
session.Query<SpecialAccount>().FetchMany(x => x.SpecialItems).ToFuture();
var accounts = session.Query<Account>().ToFuture();
Both queries run when you enumerate accounts. We don't store the results of the first query explicitly, but the SpecialAccounts are loaded in memory with their corresponding SpecialItems collections, so there are no extra DB calls.

Related

LINQ to NHibernate does not JOIN with subclass, whereas QueryOver does

I had this QueryOver query:
var result = session.QueryOver<TopicSelection>()
.Where(x => x.LacId == lac && x.RemoveDate == null)
.List();
In a nutshell: TopicSelection is a base class, and one subclass has many-to-one property, with lazy=false and fetch=join.
When I used QueryOver, NHibernate created nice join and fetched additional data from many-to-one table. Which is great, one query is issued and I get everything.
When I changed it to LINQ to NHibernate:
var result = session.Query<TopicSelection>()
.Where(x => x.LacId == lac && x.RemoveDate == null)
.ToList();
executed query does not contain JOIN. What is more, every time a many-to-one property is needed, an additional select is issued.
Is is a bug in LINQ in NHibernate? Can I instruct LINQ to NHibernate query to fetch data for subclass?
What you are experiencing is a "NHibernate LINQ implementation" as is today. We can effectively do two things.
In case, that we can adjust the query to ask for a subclass, or the many-to-one property is (could be) declared on base TopicSelection, we can use .Fetch()
var result = session
//.Query<TopicSelection>()
.Query<TopicSelectionSubClass>()
.Where(x => x.LacId == lac && x.RemoveDate == null)
.Fetch(x => x.Category ) // the many-to-one property joined
.ToList();
The second approach is even better. I would suggest that almost in any case. I.e. instead of any many-to-one mapping with lazy="false" fetch="join" - let's use:
19.1.5. Using batch fetching
Change the class mapping like this:
<class name="Category" batch-size="25" ...
That will change the 1+N select into 1+1 (or 1+2). Small cite from docs:
NHibernate can make efficient use of batch fetching, that is, NHibernate can load several uninitialized proxies if one proxy is accessed (or collections). Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can tune batch fetching: on the class and the collection level.
Batch fetching for classes/entities is easier to understand. Imagine you have the following situation at runtime: You have 25 Cat instances loaded in an ISession, each Cat has a reference to its Owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call cat.Owner on each, NHibernate will by default execute 25 SELECT statements, to retrieve the proxied owners.
Read more here

Is there a way to override a collection's mapped order-by clause when paging in NHibernate?

I have an entity with a bag collection and I want to page through the entire data set. The collection has an order-by clause. When I try to eager-load the collection, NHibernate is generating SQL that is very very slow because it causes SQL Server to sort on a very non-unique property, and there is a large amount of data in the table.
The code:
var session = NHibernateSessionManager.Instance.GetSession();
session.CreateCriteria<Album>()
.SetFetchMode("Track", FetchMode.Eager)
.SetMaxResults(1000)
.SetFirstResult(1)
.AddOrder(new Order("Id", true))
.List();
The bag mapping (note the order-by attribute):
<bag name="Track" inverse="true" lazy="true" batch-size="1000" cascade="none"
order-by="TrackNumber ASC">
The relevant SQL generated:
OVER(ORDER BY track2_.TrackNumber, this_.Id) as __hibernate_sort_row
If i remove the order-by from the mapping then the SQL changes to this (much better):
OVER(ORDER BY this_.Id) as __hibernate_sort_row
So the question is: is there a way to override or remove the mapped order-by clause?
After comments from Thilak Nathen and Firo, it looks like the short answer to the question is: "No you can't do that". The long answer is to remove the order-by attribute from the mapping and add it as needed. The reason it was there is that it's always needed, except for this one unusual situation. Firo suggested overriding the factory configuration which I think will work because this is for reporting. For anyone else who wants to know how to do it, here's the code to remove an order-by from an xml mapping programmatically:
Configuration cfg = new Configuration().Configure();
var albumMapping = cfg.ClassMappings.Where(x => x.DiscriminatorValue == "Foo.Album").First();
var trackProperty = albumMapping.GetProperty("Track");
var bagMapping = ((NHibernate.Mapping.Bag)trackProperty.Value);
bagMapping.OrderBy = "";
sessionFactory = cfg.BuildSessionFactory();

NHibernate query cache not used

I am using NH 2.1.2.4.
I have the query cache setup with Fluent NHibernate like this:
MsSqlConfiguration.MsSql2005
.Cache(c => c.UseQueryCache()
.ProviderClass(typeof(NHibernate.Caches.SysCache2.SysCacheProvider)))
My criteria looks like this:
Session.CreateCriteria<Employee>()
.Add(Restrictions.Eq("Username", username))
.SetMaxResults(1)
.SetCacheable(true)
.UniqueResult<Employee>();
However, when I use SQL Profiler I can see the query is still always being executed against the database. No inserts or updates to the Employee table are being made.
Why is it not working?
My Employee mapping class specifies Employee as cachable like this:
public sealed class EmployeeDbMap : ClassMap<Employee>
{
public EmployeeDbMap()
{
....
Cache.ReadWrite();
}
}
BTW I see there are a number of related questions on Stackoverflow, but none seem to have good answers: here and here.
If you are not caching the Employee entity too, a query will be needed to load it, as the query cache stores the results as ids.
Also, NHibernate cache only works if you do all your work (including queries) inside transactions.

NHibernate - Querying from a collection of Value Types (non-Entity) to solve Select N+1

I have an entity that represents a Tweet from Twitter like so:
public class Tweet
{
public virtual long Id { get; set; }
public virtual string Username { get; set; }
public virtual string Message { get; set; }
// other properties (snip)...
public virtual ISet<long> VoterIds { get; protected set; }
}
I'm trying to run a query in NHibernate that selects a list of tweets with an additional column that denotes whether a particular user by their UserId has voted for each Tweet. When I user votes for a Tweet, it's stored in the 'VoterIds' collection above.
I'm using a collection of value Types for this, since I'm only really interested in the Twitter UserId to determine if a user has already voted for a particular tweet. Hence why it's an ISet<long> instead of ISet<Vote>
I'm trying to use projections like so:
long userId = 123;
IList<TweetReport> tweets = Session.CreateCriteria<Tweet>()
.SetProjection(Projections.ProjectionList()
.Add(Projections.Id(), "Id")
.Add(Projections.Property("Username"), "Username")
.Add(Projections.Property("Message"), "Message")
.Add(Projections.Conditional( //---- WHAT GOES HERE!!??
.SetResultTransformer(Transformers.AliasToBean<TweetReport>())
.List<TweetReport>();
I thought the correct method was to use Projections.Conditional, but I'm not sure how to use it. Can someone help me fill in the //---- WHAT GOES HERE!!?? bit in the above code.
I tried using Expressions.In:
.Add(Projections.Conditional(Expressions.In("VoterIds", new object[] { userId }),
Projections.Constant(true), Projections.Constant(false)))
...but it gave me a 'Cannot use collections with InExpression' error. Please help!
Update: I'm beginning to think that it isn't possible to query collections of value types at all, and that I should be using a full-blown entity like so:
public virtual ISet<Vote> Votes { get; protected set; }
...would this be the case?
You can do that, but modifying the domain model to get around a limitation of NHibernate is painful to the soul. It's possible to query value collections with HQL, but ICriteria is really handy for constructing queries with logic. The only way I know how to query value collections using ICriteria is with custom SQL. This is painful also, and ties your code to your database (!), but to me it's the lesser of the three evils. My rationale is that ICriteria will eventually allow this sort of query and the pain can be refactored out later.
The trick is to use a subquery in the custom SQL so that a join to the collection table is possible. Using a table alias that won't step on NHibernate aliases is also a good idea (in this case custom_sql_t_v). And note the {alias} and ? placeholders which NHibernate will swap out.
Here's an example based on the assumption your Tweet class is mapped something like this...
<class name="Tweet" table="Tweet">
<id name="Id" unsaved-value="0">
<generator class="identity"/>
</id>
<version name="Version" unsaved-value="0"/>
<property name="UserName"/>
<property name="Message"/>
<set name="Votes" table="Tweet_Votes">
<key column="Tweet"/>
<element type="Int64" column="Vote"/>
</set>
</class>
Here's the modified query using T-SQL (i.e. Microsoft SQL Server)...
IList<TweetReport> tweets = Session.CreateCriteria<Tweet>()
.SetProjection(Projections.ProjectionList()
.Add(Projections.Id(), "Id")
.Add(Projections.Property("UserName"), "UserName")
.Add(Projections.Property("Message"), "Message")
.Add(Projections.Conditional(
Expression.Sql(
"EXISTS (SELECT 1 FROM [Tweet_Votes] custom_sql_t_v WHERE custom_sql_t_v.[Tweet] = {alias}.[Id] AND custom_sql_t_v.[Vote] = ?)",
userId,
NHibernateUtil.Int64),
Projections.Constant(true),
Projections.Constant(false)), "DidVote"))
.SetResultTransformer(Transformers.AliasToBean<TweetReport>())
.List<TweetReport>();
The final SQL generated by NHibernate (I used NHibernate 2.1.2.4000) looks like this...
exec sp_executesql N'SELECT this_.Id as y0_, this_.UserName as y1_, this_.Message as y2_, (case when EXISTS (SELECT 1 FROM [Tweet_Votes] custom_sql_t_v WHERE custom_sql_t_v.[Tweet] = this_.[Id] AND custom_sql_t_v.[Vote] = #p0) then #p1 else #p2 end) as y3_ FROM Tweet this_',N'#p0 bigint,#p1 char(1),#p2 char(1)',#p0=123,#p1='Y',#p2='N'
The upside of all this is that doing a LIKE against a string collection is possible -- something that I don't think can be done with HQL.

NHibernate - define fetching strategy dynamically

Let me explain the problem - hopefully I have defined it well in the title but I want to be sure.
I have a linq query that pulls back a bunch of objects (say Foos). Each Foo holds a reference to a User. Each User holds a reference to a Person:
public class Foo
{
//properties omitted...
public User CreatedBy {get;}
}
public class User
{
//properties omitted...
public Person Person {get;set;}
}
As the object structure would suggest, in the database, Foo relates many-to-one to User, and User relates many-to-one to Person.
When I run the query, I get a single SELECT for the Foos, then a SELECT each for all the Users and People. Clearly I would much prefer a single SELECT with a couple of joins.
I don't necessarily want to specify in my mapping config that Foos ALWAYS eager fetch the User, or that Users ALWAYS eager fetch the Person, but I would like to be able to specify that in this instance.
Is there a way to do that?
Thanks
David
All the NHibernate query methods have ways of specifying eager fetching.
For Criteria, you have SetFetchMode.
For HQL, you have [inner|left] join fetch.
For Linq yo have Expand (2.x contrib) / Fetch (3.x).
For SQL you have AddJoin.
Both Udi Dahan and Ritesh Rao offer example implementations of dynamic fetching strategies for NHibernate, this should give you a good starting point.
Additionally to Diegos nice answer: You can also use batching. This reduces the N+1 problem without much pain:
use batch-size on class level:
<class name="Person" batch-size="20">
...
</class>
use batch-size on collection level:
<map
name="SomeCollection"
batch-size="20">
...
</map>
When ever one of these references is loaded, NHibernate loads 20 at once using a query like this:
select ... from Person where user_fk in (23, 34, 6, 667, 6745, 234 ....)
So it turns N+1 to N / 20 + 1, which is pretty good.