Я построил следующий запрос, используя NHibernate
, который даст мне коллекцию MenuView
элементов, которые содержат данную страницу (на которую ссылается 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>();
Это отлично работает; однако теперь я хочу сделать наоборот: я хочу запросить все редактируемые объекты меню, которые не содержат страницу с указанным идентификатором. Я до сих пор не нашел решения для этого. Я бы подумал, что будет достаточно простого изменения раздела страниц в приведенном выше запросе, что приведет к:
// 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>();
Но это приводит к следующему 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 */)
Который все еще возвращает результаты пунктов меню, которые do содержат страницу с идентификатором 8. Почему это простое изменение логики не так просто с точки зрения кода?
[Update]
Принимая предложения от Firo, предлагаемое изменение запроса на;
// Only retrieve menus that do NOT contain this referenced page
.CreateCriteria("Pages")
.Add(Subqueries.PropertyNotIn("Id", querymenuItemswithPage)) <--- query you have would be here
Теперь генерирует следующий 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 page1_.ID not in (SELECT this_0_.ID as y0_
FROM [Page] this_0_
WHERE this_0_.ID = 1 /* @p1 */
)
Что на первый взгляд кажется именно тем, что я хотел, но, к сожалению (вероятно, из-за моего плохого понимания объединений), все еще не возвращается совсем то, что я хотел. Учитывая следующие таблицы
Меню
А затем таблица соединений PagesInMenu (с предложением WHERE WHERE PageID = 1)
Мы видим, что на страницы с идентификатором 1 НЕ ссылаются в меню 5 и 6. Я ожидаю, что рассматриваемый запрос вернет только одну строку, которая будет идентификатором, именем и описанием меню с идентификатором 5 в качестве это единственное меню, для которого страница 1 не включена, а редактируемая
Вместо этого возвращается новый запрос;
Я вычеркнул все строки, которые возвращены, но не должны быть. Что здесь происходит!?