DEV Community

Railson Ferreira de Souza
Railson Ferreira de Souza

Posted on • Edited on

Busca universal em PT-BR com RavenDB

Na língua portuguesa podemos observar vários tipos de acentos presentes nas palavras, como nas palavras 'Macarrão' e 'Emoção', repare que além do acento, existem caracteres especiais como o 'ç'.

Quando queremos utilizar um filtro em uma busca, normalmente esperamos que estas particularidades da língua sejam ignoradas, assim como não queremos que uma letra maiúscula seja diferenciada de uma minúscula.

Nas buscas de index com RavenDB conseguimos definir um analisador case insensitive, mas para os acentos da língua portuguesa precisamos desenvolver um analisar personalizado. Para isto precisamos criar um projeto .NET vazio, no Visual Studio pode-se criar um projeto Console e apagar o 'Program.cs'. Com o projeto limpo, vamos adicionar dois analisadores, um que será usado no full-text search e outro para pesquisa de palavras simples ou composta.

1. Criar Analisadores

Com o projeto criado é necessário instalar a dependência de Lucene. Agora já podemos criar os dois analisadores, segue abaixo o código de cada um deles.
Analisador 1:

class AsciiAnalyzer : StandardAnalyzer
{
    public AsciiAnalyzer()
        : base(Version.LUCENE_30)
    { }

    public AsciiAnalyzer(Version matchVersion)
        : base(matchVersion)
    { }

    public AsciiAnalyzer(Version matchVersion, FileInfo stopWords)
        : base(matchVersion, stopWords)
    { }

    public AsciiAnalyzer(Version matchVersion, ISet<string> stopWords)
        : base(matchVersion, stopWords)
    { }

    public AsciiAnalyzer(Version matchVersion, TextReader stopWords)
        : base(matchVersion, stopWords)
    { }

    public override TokenStream TokenStream(string fieldName, TextReader reader)
    {
        //Usar tokenizer do StandardAnalyzer
        var stream = base.TokenStream(fieldName, reader);

        //Filtros
        stream = new LowerCaseFilter(stream);
        stream = new ASCIIFoldingFilter(stream);

        return stream;
    }

}
Enter fullscreen mode Exit fullscreen mode

Analisador 2:

class AsciiSingleTokenAnalyzer : KeywordAnalyzer
{
    public override TokenStream TokenStream(string fieldName, TextReader reader)
    {
        //Usar tokenizer do KeywordAnalyzer
        var stream = base.TokenStream(fieldName, reader);

        //Filtros
        stream = new LowerCaseFilter(stream);
        stream = new ASCIIFoldingFilter(stream);

        return stream;
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Compilar e instalar analisadores

O projeto agora precisa ser compilado no modo 'release' para gerar um arquivo '.dll'. Realize a compilação e localize o arquivo '.dll' do seu projeto, em seguida copie apenas este arquivo e coloque na pasta 'Server' do RavenDB.

3. Criar Map-Reduce Index

Para utilizar os analisadores, devemos informar isto na criação de um Index. O código abaixo mostra um exemplo de um Map-Redude Index que usa o Analisador criado anteriormente.

public class Curriculo_Cargos : AbstractIndexCreationTask<Curriculo, Curriculo_Cargos.Result>
{
    public class Result
    {
        public string Nome { get; set; }
        public string[] Empresas { get; set; }
    }

    public Curriculo_Cargos()
    {
        Map = curriculos =>
                from curriculo in curriculos
                where curriculo.ExperienciasProfissionais != null
                from experienciaProfissional in curriculo.ExperienciasProfissionais
                select new Result
                {
                    Nome = experienciaProfissional.DescricaoDoCargo,
                    Empresas = new string[] {experienciaProfissional.NomeDaEmpresa}
                }
            ;

        Reduce = results => from result in results
            group result by result.Nome
            into g
            select new Result
            {
                Nome = g.First().Nome,
                Empresas = g.Select(x => x.Empresas).Select(x => x[0]).Distinct().ToArray()
            };

        Analyzers.Add(x => x.Nome, "NomeProjeto.Namespace.AsciiSingleTokenAnalyzer, NomeProjeto");
    }
}
Enter fullscreen mode Exit fullscreen mode

Repare que neste caso eu utilizo o AsciiSingleTokenAnalyzer, pois quero analisar 'Nome' como um todo, ao invés de palavra por palavra(full-text search).

4. Realizar uma busca

Para realizar uma busca, eu utilizo RAW Query para escrever em linguagem RQL em vez de LINQ, visto que LINQ traz uma certa limitação de operações lógicas envolvendo 'where' e 'search' em uma mesma query. O código abaixo mostra um exemplo de uma query em um Index com o analisador personalizado.

var query = _dbSession.Query<Curriculo_Cargos.Result, Curriculo_Cargos>();
if (!String.IsNullOrEmpty(request.IniciaCom))
    query = query.Search(x => x.Nome, $"{request.IniciaCom}*");
if (!String.IsNullOrEmpty(request.Contem))
    query = query.Search(x => x.Nome, $"*{request.Contem}*");
if (!String.IsNullOrEmpty(request.TerminaCom))
    query = query.Search(x => x.Nome, $"*{request.TerminaCom}");
var resposta = await query.ToArrayAsync();
Enter fullscreen mode Exit fullscreen mode

Obs.: Utilizo 'where' para busca exata de palavras compostas, pois o 'search', automaticamente separa a palavra composta da busca em um array de palavras.

Top comments (0)