The answer is quite simple all you need is to add lazy='extra' on your mappings. Lets put this to the test, for the following domain:-
public class Movie : Entity { public virtual string Name { get; set; } public virtual string Director { get; set; } public virtual IList<Actor> ActorList { get; set; } } public class Actor : Entity { public virtual string Name { get; set; } public virtual string Role { get; set; } }The only mapping of importance is the mapping for the movie:-
public class MovieMapping : SubclassMapping<Movie> { public MovieMapping() { Property(x => x.Name, x => x.NotNullable(true)); Property(x => x.Director, x => x.NotNullable(true)); Bag(x => x.ActorList, bag => { bag.Key(k => { k.Column(col => col.Name("MovieId")); k.NotNullable(true); }); bag.Cascade(Cascade.All | Cascade.DeleteOrphans); bag.BatchSize(10); }, action => action.OneToMany()); } }We now have the following code that retrieves a Movie and then the count of all actors:-
var movie = Session.Query<Movie>() .Where(w => w.Id == id).SingleOrDefault(); var actorCount = movie.ActorList.Count();This actually will send two queries to the database:-
-- statement #1 select movie0_.Id as Id1_, movie0_.Name as Name1_, movie0_.Director as Director1_ from Movie movie0_ where movie0_.Id = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */ -- statement #2 SELECT actorlist0_.MovieId as MovieId1_, actorlist0_.Id as Id1_, actorlist0_.Id as Id2_0_, actorlist0_.Name as Name2_0_, actorlist0_.Role as Role2_0_ FROM ActorRole actorlist0_ WHERE actorlist0_.MovieId = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */As you can see this is NOT the intended results we would expect. We have the first SQL query that retrieves a Movie then the second SQL query performs a select * from ActorRole. This means that the second query selects the whole collection and performs the count in memory.
Is there a easy solution that we can use to instruct NHibernate to use a select count(*) from rather than a select * ? If you read the title of this Blog and are still awake, you are probably screaming out lazy='extra'. So how do we use this?
If you are using XML mappings then you would use:-
If you are using the new mapping by code then you can achieve the same by adding:-...
Bag(x => x.ActorList, bag => { bag.Key(k => { k.Column(col => col.Name("MovieId")); k.NotNullable(true); }); bag.Cascade(Cascade.All | Cascade.DeleteOrphans); bag.BatchSize(10); bag.Lazy(CollectionLazy.Extra); }, action => action.OneToMany());Now lets look at the SQL that gets sent to the database:-
-- statement #1 select movie0_.Id as Id1_, movie0_.Name as Name1_, movie0_.Director as Director1_ from Movie movie0_ where movie0_.Id = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */ -- statement #2 SELECT count(Id) FROM ActorRole WHERE MovieId = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */It should also be noted that it does not matter if you are using HQL, Query, QueryOver or ICriteria to query your data the outcome is exactly the same. Don't you just love it when being able to tweak the mappings so as to optimise your code.
Beware that lazy="extra" combined with batch-size does not give advantages of both: on children count, you still have one query per parent entity. (But children entity loading still benefits from batch-size functionality.)
ReplyDeleteGrreat post
ReplyDelete