NHibernate Como faço para consulta contra um IList propriedade?
-
06-07-2019 - |
Pergunta
Eu estou tentando consulta contra uma propriedade IList
public class Demo
{
public Demo()
{
this.Tags = new List<string>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<string> Tags { get; set; }
}
mapeada dessa forma:
<class name="Demo">
<id name="Id" />
<property name="Name" />
<bag name="Tags">
<key column="DemoId"/>
<element column="Tag" type="String" />
</bag>
E eu sou capaz de salvar e recuperar muito bem. Agora a consulta para casos de minha classe de domínio onde o Tag propriedade contém um valor especificado:
var demos = this.session.CreateCriteria<Demo>()
.CreateAlias("Tags", "t")
.Add(Restrictions.Eq("t", "a"))
.List<Demo>();
resulta em erro: coleção não era uma associação: Demo.Tags
var demos = (from d in this.session.Linq<Demo>()
where d.Tags.Contains("a")
select d).ToList();
Os resultados do erro:. Referência Objct não definida para uma instância de um objeto
var demos = this.session.CreateQuery("from Demo d where :t in elements(d.Tags)")
.SetParameter("t", "a")
.List<Demo>();
funciona bem, mas como a minha classe de domínio real tem muitas muitas propriedades, e eu estou construindo uma consulta dinâmica complicada, fazendo a manipulação de cadeia feio não é a minha primeira escolha. Eu prefiro usar ICriteria ou Linq. Eu tenho uma interface de usuário onde muitos critérios de pesquisa diferentes possíveis podem ser inseridos. O código que constrói o ICriteria agora é dezenas de linhas. Eu realmente odeio a transformar isso em seqüência de manipulação HQL.
Solução
Assim, devido a limitações da API Criteria, decidi dobrar minhas classes de domínio para o ajuste.
Eu criei uma classe de entidade para o Tag. Eu não poderia mesmo criá-la como um objeto de valor. Ele tinha que ter o seu próprio ID.
Eu me sinto sujo agora. Mas ser capaz de construir uma consulta dinâmica, sem recorrer a manipulação de cadeia era mais importante para mim do que permanecer fiel ao domínio.
Outras dicas
Como documentado aqui:
17.1.4.1. Alias ??e propriedade referências
podemos usar:
...
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}
...
há um pequeno bug no doc ... em vez de ".element"
temos que usar ".elements"
var demos = this.session.CreateCriteria<Demo>()
.CreateAlias("Tags", "t")
// instead of this
// .Add(Restrictions.Eq("t", "a"))
// we can use the .elements keyword
.Add(Restrictions.Eq("t.elements", "a"))
.List<Demo>();
Você precisa usar SubCriterias não de alias. Isso deve funcionar:
var demos = this.session.CreateCriteria<Demo>()
.CreateCriteria("Tags")
.Add(Restrictions.Eq("Tag", "a"))
.List<Demo>();
HQL:
from Demo d where :val in elements(d.Tags)
A mudança para uma classe mais de uma string é um compromisso. Usando HQL em vez de ICriteria é outra. Há um terceiro compromisso no entanto ... uso personalizado SQL. Tentar fazer isso.
var demos = Session.CreateCriteria<Demo>()
.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<Demo>();
Isso resulta na SQL follwing sendo gerado pelo 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'
Veja este post para um outro exemplo ...
Isto é possível através da criação de um critério separadas:
ICriteria demoCriteria = session.CreateCriteria<Demo>();
...
demoCriteria.Add(Restrictions...);
...
ICriteria tagCriteria = demoCriteria.CreateCriteria("Tags");
tagCriteria.Add(Restrictions.In("elements", new {"Tag1", "Tag2", ...}));
return demoCriteria.List<Demo>();