问题
We have a situation where we are trying to retrieve a couple of levels deep for an object graph using QueryOver. So if our top level is ParentEntity and our children are ChildEntitysA, ChildEntitysB and ChildEntitysC then we have something like the following code to retrieve our graph.
var query = session.QueryOver<ParentEntity>(() => pAlias).Where(() => pAlias.Id == key).Future<ParentEntity>();
var queryA = session.QueryOver<ParentEntity>(() => pAlias).Left.JoinAlias(x => x.ChildEntitysA, () => caAlias).Where(() => pAlias.Id == key).Future<ParentEntity>();
var queryB = session.QueryOver<ParentEntity>(() => pAlias).Left.JoinAlias(x => x.ChildEntitysB, () => cbAlias).Where(() => pAlias.Id == key).Future<ParentEntity>();
var queryC = session.QueryOver<ParentEntity>(() => pAlias).Left.JoinAlias(x => x.ChildEntitysC, () => ccAlias).Where(() => pAlias.Id == key).Future<ParentEntity>();
return query.ToList();
This should generate 4 SQL statements in one single call to the database and brings back what we want. However, the SQL it generates does have some inefficiency as all of the child queries will contain in the SELECT statement all the columns of the parent as well as the child entity's columns, something akin to:
SELECT parent.col0, parent.col1, parent.col2, parent.col3, .... parent.col99
FROM parent WHERE .....
SELECT parent.col0, parent.col1, parent.col2, parent.col3, .... parent.col99, childA.col0, childA.col1, childA.col2 .... childA.col99
FROM parent LEFT OUTER JOIN childA ON ....
SELECT parent.col0, parent.col1, parent.col2, parent.col3, .... parent.col99, childB.col0, childB.col1, childB.col2 .... childB.col99
FROM parent LEFT OUTER JOIN childB ON ....
SELECT parent.col0, parent.col1, parent.col2, parent.col3, .... parent.col99, childC.col0, childC.col1, childC.col2 .... childC.col99
FROM parent LEFT OUTER JOIN childC ON ....
yet the query generated for the parent entity already contains all the necessary data! This does become a bit of an efficiency concern if we're returning multiples of the parent and the volume of data per child query rises accordingly (especially as the entities involved are quite large in terms of the number of underlying columns).
So, is there any way we can force nHibernate to not generate SQL to return all the parent entity's values for every single part of the query and just limit it to returning minimal key columns only in the SQL? Is Queryover even the optimal API for this situation?
回答1:
The way I would suggest, is to use full power of NHibernate features in these combinations:
- The first is a complex query.
- The second is to profit from
lazy
loading andbatch-size
setting
So, in the first case we are about to create one and only one QueryOver
(maybe including some subqueries)
It must contain projections, that's essential. it can include (many) Left, Inner joins, can be deeply filtered... We will then get the narrowed SELECT statement, containing only required fields. This results in one SQL query, which must be converted into some (unmapped) DTO object
The Projections and DTO means, that at the end we have to use ResultTransformer
, which will convert all coming selected fields into DTO object properties.
In the second case, we are trying to profit from lazy behaviour and batch-size
mapping. It could look like this:
1) class/ entity
<class name="ParentEntity" batch-size="25" lazy="true">
...
2) collection
<bag name="Children" lazy="true" batch-size="25"
...
See more in documentation: 19.1.5. Using batch fetching
What we gain here is, that we can create soft queries returning only light objects (these which were mapped). This is the first Select. Then all the properties (many-to-one, one-to-many) are loaded in batches - but only if they are accessed, needed.
This leads to more than 1 SELECT clause, but also there is no inflation of SELECT clauses like 1 + N. If NHibernate will find out, that some of the required properties is already loaded in the session
... it won't fire the SELECT any more.
Summary: both approaches are really the way. Try to play arround to find out in which situation you get more from the first or the second. BUT the mapping with lazy="true"
and batch-size="25"
should be used anyway
Some more links about batch-size:
- How to Eager Load Associations without duplication in NHibernate?
- NHibernate QueryOver with Fetch resulting multiple sql queries and db hits
- Is this the right way to eager load child collections in NHibernate
来源:https://stackoverflow.com/questions/23126524/nhibernate-queryover-is-there-any-way-of-getting-better-sql-out-of-nhibernate