I have constructed the following query using NHibernate
which will give me a collection of MenuView
items which contain a given page (referenced by the pages id).
// Only retrieve the required properties from Menu object
ProjectionList menuViewProjections = Projections.ProjectionList()
.Add(Projections.Property("ID"), "ID")
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Description"), "Description");
var menus = session.CreateCriteria(typeof(Menu))
// Only menu's that are editable
.Add(Restrictions.Eq("IsEditable", true))
// Only project required properties
.SetProjection(menuViewProjections)
// Only menu's that contain this page (Menu object has IList<Page> property called 'Pages')
.CreateCriteria("Pages")
// Restrict to menu's containing the pages with an id of the specified value
.Add(Restrictions.Eq("ID", pageId))
// Transform results into required, light-weight, view objects
.SetResultTransformer(Transformers.AliasToBean(typeof(MenuView)))
.List<MenuView>();
This works fine; however, now I want to do the opposite: I want to query for all editable menu objects that do not contain the page with specified ID. I have thus far not found a solution for this. I would have thought a simple reversal of the pages section of the above query would suffice resulting in:
// Only retrieve the required properties from Menu object
ProjectionList menuViewProjections = Projections.ProjectionList()
.Add(Projections.Property("ID"), "ID")
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Description"), "Description");
var menus = session.CreateCriteria(typeof(Menu))
// Only menu's that are editable
.Add(Restrictions.Eq("IsEditable", true))
// Only project required properties
.SetProjection(menuViewProjections)
// Only retrieve menus that do NOT contain this referenced page
.CreateCriteria("Pages")
.Add(Restrictions.Not(Restrictions.Eq("ID", pageId)))
// Transform results into required view objects
.SetResultTransformer(Transformers.AliasToBean(typeof(MenuView)))
.List<MenuView>();
But, this results in the following SQL:
SELECT this_.ID as y0_,
this_.Name as y1_,
this_.Description as y2_
FROM [Menu] this_
inner join PagesInMenu pages3_
on this_.ID = pages3_.MenuID
inner join [Page] page1_
on pages3_.PageID = page1_.ID
WHERE this_.IsEditable = 1 /* @p0 */
and not (page1_.ID = 8 /* @p1 */)
Which is still returning results of Menu items that do contain a page with id of 8. Why is this simple reversal of logic not so simple in terms of code?
[Update] Taking on the suggestions from Firo, the suggested query alteration to;
// Only retrieve menus that do NOT contain this referenced page
.CreateCriteria("Pages")
.Add(Subqueries.PropertyNotIn("Id", querymenuItemswithPage)) <--- query you have would be here
Now generates the following sql statement;
SELECT this_.ID as y0_,
this_.Name as y1_,
this_.Description as y2_
FROM [Menu] this_
inner join PagesInMenu pages3_
on this_.ID = pages3_.MenuID
inner join [Page] page1_
on pages3_.PageID = page1_.ID
WHERE this_.IsEditable = 1 /* @p0 */
and page1_.ID not in (SELECT this_0_.ID as y0_
FROM [Page] this_0_
WHERE this_0_.ID = 1 /* @p1 */
)
Which at first seems exactly what I wanted but sadly (probably due to my poor understanding of joins) is still not returning quite what I wanted. Given the following tables
Menu
And then the join-table of PagesInMenu (with a WHERE clause of WHERE PageID = 1)
We can see that page with id of 1 is NOT referenced in menus 5 and 6. I expect the query in question to only return a single row, which would be the ID, Name and Description of Menu with ID of 5 as this is the only menu which page 1 is not included in and which is editable
Instead, the new query returns;
I have crossed out all the rows that are returned but shouldnt be. What is going on here !?
Update:
.CreateCriteria("Pages")
added subquery
var querymenuItemswithPage = DetachedCriteria.For<Menu>()
.CreateCriteria("Pages")
.Add(Restrictions.Eq("ID", pageId))
.SetProjection(Projections.Id())
// Only retrieve the required properties from Menu object
ProjectionList menuViewProjections = Projections.ProjectionList()
.Add(Projections.Property("ID"), "ID")
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Description"), "Description");
var menus = session.CreateCriteria(typeof(Menu))
// Only menu's that are editable
.Add(Restrictions.Eq("IsEditable", true))
// Only project required properties
.SetProjection(menuViewProjections)
// Only retrieve menus that do NOT contain this referenced page
.Add(Subqueries.PropertyNotIn("Id", querymenuItemswithPage))
// Transform results into required view objects
.SetResultTransformer(Transformers.AliasToBean(typeof(MenuView)))
.List<MenuView>();