QueryOver Specification: reusando critérios
Autor: Rafael | Publicado: 2012-10-28T04:22:00-03:00

É muito comum termos que duplicar partes dos critérios feitos em consultas a banco de dados para atender regras de negócio. O Specification pattern tenta resolver esse problema agrupando esses critérios em classes, e usa uma composição de specifications para unir todos os critérios. QueryOverSpecification é uma pequena biblioteca implementando uma variação do padrão para uso com NHibernate QueryOver.






Specification Pattern

Basicamente o padrão é uma interface com um método que diz se a entidade satisfaz ou não um critério, ou seja, um predicado sobre um objeto.

public&nbsp;interface&nbsp;ISpecification
{
&nbsp;&nbsp;&nbsp;&nbsp;bool&nbsp;IsSatisfiedBy(object&nbsp;candidate);
}
 
public&nbsp;class&nbsp;CourseWithVotes&nbsp;:&nbsp;ISpecification
{
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;bool&nbsp;IsSatisfiedBy(Course&nbsp;course)
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;course.Votes.Count&nbsp;&gt;&nbsp;0;
&nbsp;&nbsp;&nbsp;&nbsp;}
}


O problema com essa abordagem é que a implementação pode causar problemas de performance se usado em um base de dados com uma grande quantidade de registros, já que para cada registro, deverá ser feita a chamada do método para verificação se o objeto satisfaz ou não a Specification.

Uma alternativa para .NET é o uso do LINQ, mas para projetos que preferivelmente seja melhor o uso do QueryOver, a biblioteca QueryOverSpecification dá o suporte necessário para o reuso das consultas.

Domain

Fazendo esse mesmo exemplo usando QueryOverSpecification, teríamos:

&nbsp;&nbsp;&nbsp; public&nbsp;interface&nbsp;ICourseWithVotes
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ISpecification&lt;Course&gt;&nbsp;By();
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;
&nbsp;&nbsp;&nbsp; public&nbsp;class&nbsp;CourseWithVotes&nbsp;:&nbsp;ICourseWithVotes
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;ISpecification&lt;Course&gt;&nbsp;By()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Action&lt;IQueryOver&lt;Course,&nbsp;Course&gt;&gt;&nbsp;action&nbsp;=&nbsp;x&nbsp;=&gt;&nbsp;x.JoinQueryOver(y&nbsp;=&gt;&nbsp;y.Votes);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;new&nbsp;QueryOverSpecification&lt;Course&gt;(action);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

Veja que mudamos a forma como a Specification é usada. Criamos um delegate action sobre uma instância de QueryOver de Course, ou seja, o que eu devo adicionar ao QueryOver para efetuar o critério sobre o Course. Passamos essa action para a classe QueryOverSpecification, que cria uma specification usando um QueryOver.

Repository

Abaixo segue um exemplo de um método Find() genérico da classe Repository, que recebe uma ISpecification e retorna a lista de objetos que satisfazem ao critério:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;IList&lt;TEntity&gt;&nbsp;Find&lt;TEntity&gt;(ISpecification&lt;TEntity&gt;&nbsp;spec)&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;where&nbsp;TEntity&nbsp;:&nbsp;class,&nbsp;IIdAccessor
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;session&nbsp;=&nbsp;Factory.OpenSession();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;query&nbsp;=&nbsp;session.QueryOver&lt;TEntity&gt;();
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;visitor&nbsp;=&nbsp;new&nbsp;QueryOverSpecificationVisitor&lt;TEntity&gt;(query);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;spec.Accept(visitor);
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;list&nbsp;=&nbsp;query.List();
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.Close();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;list;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}


*Esse método é somente um exemplo com nenhum tratamento de exception ou transaction. 

QueryOverSpecificationVisitor é uma classe que recebe um QueryOver e adiciona todos os critérios necessários para efetuar a consulta, depois é só executar o método List() do QueryOver para ter a lista de objetos.

Service

Dentro de uma classe Service, teríamos a chamada do Repository:

var&nbsp;list&nbsp;=&nbsp;_repository.Find(courseWithVotes.By());


Operadores

Criando outras Specifications, podemos uni-lás usando os operadores And e Or, e até passar parametros para o método By():

var&nbsp;list&nbsp;=&nbsp;_repository.Find(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;courseWithVotes.By()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.And(courseByExample.By(new&nbsp;Course()&nbsp;{&nbsp;Name&nbsp;=&nbsp;"C#"&nbsp;})));

IIdAccessor

Uma restrição da biblioteca que é todas as suas entidades devem implementar a interface IIdAccessor. Ela descreve um Id único para aquela entidade.

namespace&nbsp;Naskar.QueryOverSpec
{
&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;interface&nbsp;IIdAccessor
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long?&nbsp;Id&nbsp;{&nbsp;get;&nbsp;set;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}
}

Para maioria dos projetos, uma simples classe base abstrata para suas entidades já resolve essa restrição:

&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;abstract&nbsp;class&nbsp;Entity&nbsp;:&nbsp;IIdAccessor
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;virtual&nbsp;long?&nbsp;Id&nbsp;{&nbsp;get;&nbsp;set;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp; public&nbsp;class&nbsp;Course&nbsp;:&nbsp;Entity
&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Course()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.Votes&nbsp;=&nbsp;new&nbsp;List&lt;Vote&gt;();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;virtual&nbsp;string&nbsp;Name&nbsp;{&nbsp;get;&nbsp;set;&nbsp;}
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;virtual&nbsp;Instructor&nbsp;Instructor&nbsp;{&nbsp;get;&nbsp;set;&nbsp;}
 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;virtual&nbsp;IList&lt;Vote&gt;&nbsp;Votes&nbsp;{&nbsp;get;&nbsp;set;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;}

Atualmente, temos quatro tipos de formas de criar Specifications e três operadores para uní-las:




- QueryOverSpecification: usa um QueryOver para fazer os critérios e é preferível por padrão, como mostrado acima.

- LambdaSpecification: usa uma expressão para adicionar o critério. 

new&nbsp;LambdaSpecification&lt;Course&gt;(x&nbsp;=&gt;&nbsp;x.Name.IsInsensitiveLike("ava",&nbsp;MatchMode.Anywhere))



- CriterionSpecification: usa a API Criterion do NHibernate para adicionar os criterios.

new&nbsp;CriterionSpecification&lt;Instructor&gt;(Property.ForName("Name").Like("ciclano",&nbsp;MatchMode.Anywhere))



- NoRestrictionsSpecification: não adiciona nenhum critério. Útil para criação de Specifications condicionais. 

Operador With

With é um operador adicional que inclui um critério a uma propriedade de uma entidade sem a necessidade de fazer um Join usando o QueryOver.

Exemplo:
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;list&nbsp;=&nbsp;_repository.Find(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;courseWithVotes.By().With(x&nbsp;=&gt;&nbsp;x.Instructor,&nbsp;instructorByExample.By(new&nbsp;Instructor()&nbsp;{&nbsp;Name&nbsp;=&nbsp;"fulano"&nbsp;})))

Liste todos os 'cursos com votos' com o 'instrutor que tem o nome parecido com fulano'.

*Atualmente, o operador With não tem suporte a propriedades IEnumerable.

Conclusão

QueryOverSpecification é uma simples biblioteca para construir classes com critérios para consultas em base de dados usando NHibernate de forma a aumentar o reuso e diminuir a complexidade das consultas sem problemas de performance.

As interfaces criadas como ICourseWithVotes, são conceitos e regras vindos dos requisitos e dos casos de uso e que podem conviver sem dependências com frameworks dentro da camada de modelo (domain), deixando a implementação das specifications separada do modelo, preferivelmente em outro namespace e/ou assembly.



Referências

Source no codeplex: http://queryoverspec.codeplex.com/SourceControl/BrowseLatest
Nuget: http://nuget.org/packages/Naskar.QueryOverSpec