Filtros dinâmicos usando CAML

Olá pessoal!

Bom, em nosso dia-a-dia é comum precisarmos fazer filtros em alguma lista. O problema começa a acontecer quando os critérios de filtro podem variar. Quando isso ocorre é uma dor de cabeça. Para solucionar este problema existe uma ferramenta (?) muito interessante: o Camlex.

De uma forma bem sucinta o Camlex pega sua expressão lambda e converte para CAML e vice-versa.

Exemplo de conversão:

Explicando um pouco o código:
var query = Camlex.Query();
CamlQuery filtro = query.Where(itemfiltro => (string)itemfiltro["Title"] == "Arnaldo").ToCamlQuery();
string camlString = query.Where(itemfiltro => (string)itemfiltro["Title"] == "Arnaldo").ToString();

Linha 1: Apenas pegamos uma instância de nossa query;

Linha 2: Criamos o nosso filtro onde:

itemFiltro: É o nosso ListItem;

“Title”: É o nome interno do campo que quero filtrar;

“Arnaldo”: É o valor do campo;

ToCamlQuery: Ele converte a nossa expressão em uma CamlQuery, para ser usada com o CSOM do SharePoint.

Linha 3: A mesma query, entretanto mostrando que é possível transforma-lá em uma string.

Hora de codar!

Agora vamos mostrar na prática como isso pode funcionar. Neste exemplo estou usando uma lista chamada “Alunos” que está em um site no SharePoint OnLine.

OBS1.: O Camlex possui versão para código server site (farm solutions).

OBS2: Todo o código está disponível no GitHub em https://github.com/pedromneto/filtros-dinamicos-sharepoint-caml:

Estrutura da lista:

Nome = Campo tipo tipo texto. O Campo “Title” nativo do SharePoint foi renomeado.

Curso: Campo tipo Lookup. Este campo busca os cursos disponíveis de outra lista.

Matriculado: Campo tipo sim ou não;

Cidade: Campo tipo texto;

Primeiro vamos criar um novo projeto do tipo Console Application.

Uma vez criado o projeto vá em Tools > NuGet Package Manager > Package Manager Console:

Primeiro vamos adicionar a referência as Dlls do SharePoint OnLine, para isso digite:

Install-Package Microsoft.SharePointOnLine.CSOM

Agora vamos adicionar a referência do Camlex:

Install-Package Camlex.Client.dll

Na estrutura do nosso projeto criei 2 classes:

Credenciais: Para armazenar as credenciais para logar no SharePoint OnLine;

FiltroAlunos: Ela Armazena e exibe o resultado de nossas consultas. Ela é a principal classe de nossa aplicação e ela que vamos detalhar.

Classe credenciais:

 public abstract class Credenciais
    {
        static string _password = "achoqueaquideveriaterumasenha";
        
        public const string UserName = "naovejaisso@meusite.onmicrosoft.com";

        public static SecureString Password()
        {
            var password = new SecureString();
            _password.ToList().ForEach(c => password.AppendChar(c));
            return password;            
        }
    }
}

Classe FiltroAlunos:

static class FiltroAlunos
    {
        //Filtro pelo nome do aluno
        internal static Expression<Func<ListItem, bool>> PorNome(string nome) =>
            (item => ((string)item["Title"]) == nome);

        //Filtra pelo inicio do nome do aluno
        internal static Expression<Func<ListItem, bool>> InicioNome(string nome) =>
            (item => ((string)item["Title"]).StartsWith(nome));

        //Filtra os alunos de um determinado curso
        internal static Expression<Func<ListItem, bool>> Curso(string nome) =>
            (item => (item["Curso"] == (CamlexNET.DataTypes.LookupValue)nome));

        //Filtra os alunos matriculados
        internal static Expression<Func<ListItem, bool>> Matriculados(bool matriculado) =>
            (item => (((Boolean)item["Matriculado"])) == matriculado);

        //Filtra pelos nomes enviados
        internal static Expression<Func<ListItem, bool>> Nomes(string[] nome) =>
            (item => (nome.Contains((string)item["Title"])));

        //Filtro por campo do tipo texto
        internal static Expression<Func<ListItem, bool>> FiltroTexto(string nomeCampo, string valorFiltro) =>
            (item => (string)item[nomeCampo] == valorFiltro);

        internal static void ExibirResultado(ListItemCollection itens)
        {
            foreach (var item in itens)
            {
                Console.WriteLine($" * Nome: {item["Title"]}, Curso: {((FieldLookupValue)item["Curso"]).LookupValue}, Cidade: {item["Cidade"]}");
            }

            Console.WriteLine("=================================\n");
        }
    }

Vamos lá: Na classe FiltroAlunos criei alguns filtros interessantes:

 //Filtro pelo nome do aluno
internal static Expression<Func<ListItem, bool>> PorNome(string nome) =>
    (item => ((string)item["Title"]) == nome);

A função recebe o nome do aluno como parâmetro. Observe que convertemos o campo “Title” da lista para string (string)item[“Title”] .

Podemos manter a mesma ideia quando filtramos por outros tipos de campo, como o boolean:

//Filtra os alunos matriculados
internal static Expression<Func<ListItem, bool>> Matriculados(bool matriculado) =>
   (item => (((Boolean)item["Matriculado"])) == matriculado);

Quando queremos fazer um filtro envolvendo um array temos que inverter a estrutura da query. Assim checamos se o array contém o valor do campo:

//Filtra pelos nomes enviados
internal static Expression<Func<ListItem, bool>> Nomes(string[] nome) =>
   (item => (nome.Contains((string)item["Title"])));

Neste caso filtramos pelos possíveis nomes:

Uma opção bem interessante é o filtro por campo Lookup. Ele dá a opção de filtros por LookupId, LookupValue e campos multi-lookups:

//Filtra os alunos de um determinado curso
internal static Expression<Func<ListItem, bool>> Curso(string nome) =>
   (item => (item["Curso"] == (CamlexNET.DataTypes.LookupValue)nome));

 

Where X WhereAll X WhereAny

Bom, agora que escrevemos as nossas possíveis queries temos que fazer o filtro e gerar a caml. O Camlex dá as seguintes opções para filtro:

  • Where: Filtra utilizando apenas uma query/expressão;
  • WhereAll: Pega as diversas queries e faz o filtro utilizando o operador AND. Exemplo:
//Filtrar os alunos que começam com a letra M e que estão matriculados
var filtroMEMatriculados = new List<Expression<Func<ListItem, bool>>>();
filtroMEMatriculados.Add(FiltroAlunos.InicioNome("m"));
filtroMEMatriculados.Add(FiltroAlunos.Matriculados(true));
var alunosMeMatriculados = lstAlunos.GetItems(query.WhereAll(filtroMEMatriculados).ToCamlQuery());
  • WhereAny: Pega as diversas queries e faz o filtro utilizando o operador OR.
//Filtrar os alunos que se chamam pedro ou começam com a letra M
var filtroPedroM = new List<Expression<Func<ListItem, bool>>>();
filtroPedroM.Add(FiltroAlunos.PorNome("Pedro"));
filtroPedroM.Add(FiltroAlunos.InicioNome("M"));
var alunosPedroOuM = lstAlunos.GetItems(query.WhereAny(filtroPedroM).ToCamlQuery());

Execução da query:

using (ClientContext context = new ClientContext(url))
{
    context.Credentials = new SharePointOnlineCredentials(Credenciais.UserName, Credenciais.Password());
    var web = context.Web;
    var lstAlunos = web.GetList("/Lists/Alunos");

    context.Load(lstAlunos);
    context.ExecuteQuery();

    var query = Camlex.Query();

    //Filtrar por nome
    var filtroNome = FiltroAlunos.PorNome("Pedro");
    var pedros = lstAlunos.GetItems(query.Where(filtroNome).ToCamlQuery());

    context.Load(pedros);

    context.ExecuteQuery();
}

Linha 3: Pego as credenciais;

Linha 5: Busco a lista de alunos;

Linha 6 e 7: Carrego a lista;

Linha 10: Instancia do IQuery, do Camlex.

Linha 13: Utilizo a classe FiltroAlunos para buscar a query por nome;

Linha 14: Utilizo a instância do IQuery para adicionar o filtro query.Where(filtroNome). E, em seguida o transformo em uma CamlQuery: query.Where(filtroNome).ToCamlQuery() Por último utilizo o GetItens para executar o filtro: lstAlunos.GetItems(query.Where(filtroNome).ToCamlQuery()); .

Linha 16: Carregar os itens

Linha 18: Envio a query para o SharePoint.

Resultado:

Agora todos os resultados dos filtros:

 

Bom por hoje é só pessoal! Qualquer dúvida entrem em contato.

 

Referências:

Código fonte:

https://github.com/pedromneto/filtros-dinamicos-sharepoint-caml