One of the most feared missing feature of the ICriteria API is the possibility to specify condition on projection with Having. Suppose you have this simple connection between a container and a contained object,
The EntityContainer class has a property called Test, is an IList<EntityTest> and EntityTest has a Container property to link back to the container. This is the classic bidirectional association. Now we want to express with ICriteria API the following query
Select all EntityContainer that have more than one EntityTest in the Test collection
And I want to solve this problem using both the direction of association, I mean two criteria, one that use the many-to-one and the other that use the <bag> part. This is important because bidirectional association is not always the best solution, so it is possible to have a domain where the association is unidirectional.
Using the many-to-one part
ICriteria c = session.CreateCriteria(typeof(EntityContainer), "RootClass"); NEX.DetachedCriteria d = NEX.DetachedCriteria.For(typeof(EntityTest)) .SetProjection(NEX.Projections.RowCount()) .Add(NEX.Property.ForName("Container").EqProperty("RootClass.Id")); //You can use also //.Add(NEX.Expression.EqProperty("Encontained", "RootClass.Id")); c.Add(NEX.Subqueries.Lt(1, d)); IList<EntityContainer> result = c.List<EntityContainer>();
The result is achieved using a DetatchedCriteria and a subquery, the detatched criteria is on the EntityTest class, set a simple count projection and set the join with the original query with a Expression.EqProperty. Once the DetatchedCriteria is build I simply add it to the root Criteria using the Subqueries.Lt (Less Than). Here is the SQL
SELECT this_.Id as Id2_0_, this_.PStr as PStr2_0_, this_.RegDate as RegDate2_0_ FROM EntityContainer this_ WHERE @p0 < (SELECT count(*) as y0_ FROM EntityTest this_0_ WHERE this_0_.ContainerId = this_.Id); @p0 = '1'
The code is quite good, I simply use a subquery to calculate the number of EntityTest associated to the object and I take the EntityContainer if this value is greater than 1. (Actually the syntax force me to use 1 is less than count
)
Using the <bag> part
This is more difficult, because the direction of the association is from EntityContainer to EntityTest so the detatched criteria should be on EntityContainer.
ICriteria c = session.CreateCriteria(typeof(EntityContainer), "RootClass"); NEX.DetachedCriteria d = NEX.DetachedCriteria.For(typeof(EntityContainer)) .CreateAlias("Tests", "Tests") .SetProjection(NEX.Projections.ProjectionList() .Add(NEX.Projections.RowCount())) .Add(NEX.Property.ForName("Id").EqProperty("RootClass.Id")); c.Add(NEX.Subqueries.Lt(1, d)); IList<EntityContainer> result = c.List<EntityContainer>();
The solution is simple, I call CreateAlias to make the join with EntityTest through the Tests collection, add the projection rowCount and simply join with the original criteria with the EqProperty Id == RootClass.Id
Here is the sql generated
SELECT this_.Id as Id18_0_, this_.PStr as PStr18_0_, this_.RegDate as RegDate18_0_ FROM EntityContainer this_ WHERE @p0 < (SELECT count(*) as y0_ FROM EntityContainer this_0_ inner join EntityTest tests1_ on this_0_.Id=tests1_.ContainerId WHERE this_0_.Id = this_.Id); @p0 = '1'
And the game is done
Alk.






July 15th, 2008 at 6:26 pm
Note that it now does have Having support.
http://jira.nhibernate.org/browse/NH-1280
March 12th, 2009 at 6:00 am
I have a similiar problem, I would want to express with ICriteria:
Select all EntityContainer that have NO EntityTest in the Test collection
or in other words how to select parent that haven’t children. How it is possible to interrogate the Count property?
Thanks in advance
March 13th, 2009 at 2:28 am
The solution is really simple you need only to change the condition of the subquery
ICriteria c = session.CreateCriteria(typeof(Customer2), “RootClass”);
DetachedCriteria d = DetachedCriteria.For(typeof(Customer2))
.CreateAlias(“Orders”, “Orders”)
.SetProjection(Projections.ProjectionList()
.Add(Projections.RowCount()))
.Add(Property.ForName(“Id”).EqProperty(“RootClass.Id”));
c.Add(Subqueries.Eq(0, d)); //Here is the modification result = c.List ();
IList
Assert.That(result.Count, Is.EqualTo(123));
March 19th, 2009 at 10:00 pm
Thank you very much! I google for it for longs. Now I can have a little rest.
March 20th, 2009 at 4:58 am
Thanks! It was a little tricky to solve it.