Запрос коллекции "многие ко многим" или как включить таблицу "многие ко многим" в запрос критериев?

Я новичок в мире NHibernate, и я не могу заставить это работать с использованием запроса критериев: запросить отношение «многие ко многим» или запросить коллекцию (набор / мешок) для объекта. Я поискал в Интернете и проверил все имеющиеся у нас книги NHibernate, но не могу найти конкретного ответа на свой «вызов».

Я привел упрощенный пример проблемы, которую пытаюсь решить. У меня есть таблица с книгами, таблица с категориями и таблица «многие ко многим» с категориями для каждой книги. Вот некоторые из технических деталей:

структура данных:

create table tableBook
(
    BkId       integer   not null default autoincrement,    
    BkTitle    char(40)  not null,
    BkWriter   char(40)  not null,
    primary key (BkId)
);

create table tableCategory
(
    CatId       integer   not null default autoincrement,    
    CatCode     char(3)   not null,
    CatDesc     char(40),
    primary key (CatId)
);

create table tableCategoriesPerBook
(
    CpbId        integer         not null default autoincrement,
    CpbBkId      integer         not null, /*foreign key to tableBook*/
    CpbCatId     integer         not null, /*foreign key to tableCategory*/
    primary key (CpbId)
);

alter table tableCategoriesPerBook add foreign key FK_CpbBkId (CpbBkId) references tableBook (BkId) on update Restrict on delete Cascade;
alter table tableCategoriesPerBook add foreign key FK_CpbCatId (CpbCatId) references tableCategory (CatId) on update Restrict on delete Cascade;

create unique index idx_CpbCatId_CpbBkId on tableCategoriesPerBook (CpbCatId, CpbBkId);

Классы C #:

public class BookEntity
{
    public virtual Int32 BookId { get; set; }
    public virtual string BookTitle { get; set; }
    public virtual string BookWriter { get; set; }

    private readonly IEnumerable<CategoryEntity> _categories = new ObservableCollection<CategoryEntity>();
    public virtual IEnumerable<CategoryEntity> Categories
    {
        get { return _categories; }            
    }
}

public class CategoryEntity
{
    public virtual Int32 CategoryId { get; set; }
    public virtual string CategoryCode { get; set; }
    public virtual string CategoryDesc { get; set; }
}

Отображения NHibernate:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.BookEntity" table="tableBook">
    <id name="BookId" column="BkId" type="Int32">
      <generator class="native" />
    </id>
    <property name="BookTitle" column="BkTitle" type="string" length="40"/>
    <property name="BookWriter" column="BkWriter" type="string" length="40"/>    
    <idbag name="_categories" access="field" table="tableCategoriesPerBook">        
        <collection-id type="Int32" column="CpbId">
          <generator class="native"/>
        </collection-id>        
        <key column="CpbBkId" property-ref="BkId"/>        
        <many-to-many column="CpbCatId" class="Domain.CategoryEntity, Domain" />
    </idbag>
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.CategoryEntity" table="tableCategory">    
    <id name="CategoryId" column="CatId" type="Int32">
      <generator class="native" />
    </id>
    <property name="CategoryCode" column="CatCode" type="string" length="3" />
    <property name="CategoryDesc" column="CatDesc" type="string" length="40" />    
  </class>
</hibernate-mapping>

Мой вопрос: можно ли запросить (используя ICriteria и / или отдельные критерии) базу данных таким образом, чтобы я получил книги, которые находятся в одной из указанных мною категорий (например: в catA или catB, может быть "и " также)? Я хочу оптимизировать это в запросе, а не в C # (поскольку мне нужно прочитать все книги из базы данных, прежде чем я смогу фильтровать объекты на основе их набора тегов). Если бы я написал SQL вручную, я бы получил что-то вроде этого:

SELECT * FROM tableBook                                                                                                                           
WHERE EXISTS 
    (
     SELECT 1 
     FROM   tableCategoriesPerBook 
            INNER JOIN tableCategory on (CpbCatId = CatId and CpbBkId = BkId) 
     WHERE  CatCode in ('001', '002')
    )

Поскольку у меня нет сущности для tableCategoriesPerBook, я не вижу способа добраться до этой таблицы с запросом критериев. И я бы предпочел не добавлять рукописные фрагменты SQL-выражений, используя:

criteria.Add(Expression.Sql("exists(.....)");

И еще один важный фактор: я использую базу данных brownfield, поэтому я не могу изменить структуру! это то, с чем мне придется работать с базой данных.


person TedOnTheNet    schedule 22.03.2011    source источник


Ответы (1)


Это довольно просто. Вы можете использовать отдельные критерии.

DetachedCriteria bookCategoryCriteria = DetachedCriteria.For<BookEntity>("bookCat");
bookCategoryCriteria
    .CreateAlias("Categories", "cat", JointType.LeftOuterJoin)
    .Add(Restrictions.In("cat.CategoryCode", categories)
    .Add(Restrictions.Eq("bookCat.BookId", "book.BookId")
    .SetProjection(Projections.Id());

Session.CreateCriteria<BookEntity>("book")
   .Add(Subqueries.Exists(bookCategoryCriteria));
person Vadim    schedule 22.03.2011
comment
Привет, Ядс, большое спасибо за помощь. Я просмотрел множество других сообщений о stackoverflow, но ни один из них не приблизил меня к решению этой проблемы. Ваш пример кода почти сделал это для меня! строка .Add(Restrictions.Eq("bookCat.BookId", "book.BookId") неверна, но для меня это было легко исправить. Я заменил это строкой: .Add(Property.ForName("bookCat.BookId").EqProperty("book.BookId")). Огромное спасибо за помощь. - person TedOnTheNet; 22.03.2011