NHibernate Come si esegue una query su una proprietà IList ?

Sto cercando di eseguire una query su una proprietà ILIST su una delle mie classi di dominio usando NHibernate. Ecco un semplice esempio per dimostrare:

public class Demo { public Demo() { this.Tags = new List(); } public virtual int Id { get; set; } public virtual string Name { get; set; } public virtual IList Tags { get; set; } } 

Mappato in questo modo:

        

E sono in grado di salvare e recuperare bene. Ora per eseguire una query per le istanze della mia class di dominio in cui la proprietà Tag contiene un valore specificato:

 var demos = this.session.CreateCriteria() .CreateAlias("Tags", "t") .Add(Restrictions.Eq("t", "a")) .List(); 

Risultati nell’errore: raccolta non era un’associazione: Demo.Tags

 var demos = (from d in this.session.Linq() where d.Tags.Contains("a") select d).ToList(); 

Risultati nell’errore: Riferimento Objct non impostato su un’istanza di un object.

 var demos = this.session.CreateQuery("from Demo d where :t in elements(d.Tags)") .SetParameter("t", "a") .List(); 

Funziona bene, ma poiché la mia vera class di dominio ha molte proprietà e sto costruendo una query dynamic complicata, fare brutte manipolazioni di stringhe non è la mia prima scelta. Preferirei usare ICriteria o Linq. Ho un’interfaccia utente in cui possono essere inseriti molti diversi criteri di ricerca possibili. Il codice che costruisce l’ICriteria in questo momento è dozzine di righe. Mi dispiacerebbe davvero trasformarlo in manipolazione di stringhe HQL.

Quindi, a causa delle limitazioni dell’API dei criteri, ho deciso di piegare le mie classi di dominio per adattarle.

Ho creato una class di quadro per il tag. Non riuscivo nemmeno a crearlo come object valore. Doveva avere il suo id.

Mi sento sporco ora. Ma essere in grado di build una query dynamic senza ricorrere alla manipolazione delle stringhe era più importante per me che rimanere fedele al dominio.

Come documentato qui:

17.1.4.1. Alias ​​e riferimenti di proprietà

possiamo usare:

 ... A collection key {[aliasname].key} ORGID as {coll.key} The id of an collection {[aliasname].id} EMPID as {coll.id} The element of an collection {[aliasname].element} XID as {coll.element} ... 

c’è un piccolo bug in doc … invece di ".element" dobbiamo usare ".elements"

 var demos = this.session.CreateCriteria() .CreateAlias("Tags", "t") // instead of this // .Add(Restrictions.Eq("t", "a")) // we can use the .elements keyword .Add(Restrictions.Eq("t.elements", "a")) .List(); 

È necessario utilizzare SubCriterias non alias. Questo dovrebbe funzionare:

 var demos = this.session.CreateCriteria() .CreateCriteria("Tags") .Add(Restrictions.Eq("Tag", "a")) .List(); 

HQL:

 from Demo d where :val in elements(d.Tags) 

Passare a una class su una stringa è un compromesso. L’uso di HQL invece di ICriteria è un altro. C’è comunque un terzo compromesso … usa l’SQL personalizzato. Prova questo.

 var demos = Session.CreateCriteria() .Add(Expression.Sql( "EXISTS (SELECT 1 FROM [Tags] custom_sql_t WHERE custom_sql_t.[DemoId] = {alias}.[Id] AND custom_sql_t.[Tag] = ?)", "a", NHibernateUtil.String)) .List(); 

Ciò si traduce nel follwing SQL generato da NHibernate 2.1.2.4000 …

 exec sp_executesql N'SELECT this_.Id as Id2_0_, this_.Version as Version2_0_, this_.Name as Name2_0_ FROM Demo this_ WHERE EXISTS (SELECT 1 FROM [Tags] custom_sql_t WHERE custom_sql_t.[DemoId] = this_.[Id] AND custom_sql_t.[Tag] = @p0)',N'@p0 nvarchar(1)',@p0=N'a' 

Vedi questo post per un altro esempio …

NHibernate – Interrogazione da una raccolta di tipi di valore (non-entity framework) da risolvere Selezionare N + 1

Questo è ansible creando un criterio separato:

 ICriteria demoCriteria = session.CreateCriteria(); ... demoCriteria.Add(Restrictions...); ... ICriteria tagCriteria = demoCriteria.CreateCriteria("Tags"); tagCriteria.Add(Restrictions.In("elements", new {"Tag1", "Tag2", ...})); return demoCriteria.List();