Stai eseguendo una query su una raccolta molti-a-molti o come includere una tabella molti-a-molti in una query di criteri?
-
28-10-2019 - |
Domanda
Sono abbastanza nuovo nel mondo di NHibernate e non riesco a farlo funzionare con l'uso di una query basata su criteri: interrogare una relazione molti-a-molti o interrogare una raccolta (set / borsa) su un entità. Ho cercato in Internet e controllato tutti i libri di NHibernate che abbiamo, ma non riesco a trovare una risposta specifica alla mia "sfida".
Ho fatto un esempio semplificato del problema che sto cercando di risolvere. Ho una tabella con i libri, una tabella con le categorie e una tabella molti-a-molti con le categorie per libro. Ecco alcuni degli aspetti tecnici:
struttura dati:
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);
Classi 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; }
}
N Mappature ibernate:
<?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>
La mia domanda: è possibile interrogare (usando ICriteria e / o criteri separati) il database in modo tale da ottenere i libri che si trovano in una delle categorie da me specificate (ad esempio: in catA o catB, potrei essere "e" pure)? Voglio ottimizzarlo nella query, non in C # (poiché ho bisogno di leggere tutti i libri dal database prima di poter filtrare gli oggetti in base alla loro raccolta di tag). Se scrivessi l'SQL a mano, produrrei qualcosa del genere:
SELECT * FROM tableBook
WHERE EXISTS
(
SELECT 1
FROM tableCategoriesPerBook
INNER JOIN tableCategory on (CpbCatId = CatId and CpbBkId = BkId)
WHERE CatCode in ('001', '002')
)
Poiché non ho un'entità per tableCategoriesPerBook, non vedo un modo per arrivare a questa tabella con una query di criteri. E preferisco non aggiungere qualche parte scritta a mano di espressioni SQL usando:
criteria.Add(Expression.Sql("exists(.....)");
Un ultimo fattore importante: sto usando un database brownfield, quindi non posso cambiare la struttura! questo è ciò che dovrò lavorare con il database.
Soluzione
Questo è abbastanza semplice.Puoi utilizzare un criterio distaccato.
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));