Estratégias para projetos de Dynamics CRM/365

Olá pessoal,

Há alguns dias atrás, perguntaram sobre estratégias para trabalhar com projetos de Dynamics.

Bem longe de ser uma pergunta que tenha uma resposta certa ou apenas que tenha uma única resposta. Aqui vai este post com algumas ideias/dicas para cada tipo de projeto (pequeno, intermediário/médio e grande), baseado nas minhas experiências ao longo desses 10 anos de Dynamics que podem ser úteis à VOCÊ!

Como eu disse anteriormente, a forma de categorização de cada tipo de projeto é algo que pode variar bem de organização para organização, além de não ser uma “receita de bolo” que sempre seja possível aplicá-la em todos os cenários, minha ideia neste post é dizer o que já fizemos por onde trabalhei/trabalho.

Antes de aprofundar em cada modelo de trabalho, é necessário de alguma forma padronizar os tipos de projeto. Embora muitas pessoas acreditem que a quantidade de membros da equipe é o fator decisório para aplicarmos o modelo de projeto ideal, por experiência pessoal, descobri que não pode ser o único fator. Assim, o tamanho das equipes devem unir-se a outros dois fatores: concorrência de recursos do Dynamics e grau de complexidade.

Costumamos considerar como equipes pequenas, times de 2 à 5 pessoas, equipes intermediárias/médias de 5 à 10 pessoas e grandes equipes, acima de 10 pessoas. Não usem os números acima como uma regra máxima, e sim como uma base para iniciar a estruturar suas ideias.

A necessidade de utilização dos mesmos recursos do Dynamics por mais do que uma pessoa, é algo em que o Dynamics possibilita nativamente, mas por não bloquear a edição simultânea, acaba abrindo a possibilidade de que uma pessoa interfira no trabalhado da outra.

Um de muitos exemplos sobre isso, seria quando uma pessoa precisa adicionar novos campos na entidade Conta e outra pessoa está criando outros campos no mesmo período, com isso existe uma boa chance de um “atrapalhar o outro”. Por exemplo:

A pessoa A, abre o formulário primeiro, a pessoa B abre o mesmo formulário segundos depois. A pessoa A cria alguns novos campos e adiciona no formulário, salvando e publicando, quando a pessoa B concluir sua tarefa irá acidentalmente remover todos os campos que a pessoa A adicionou no formulário.

Assim, a chances de um projeto de pequeno porte presenciar o exemplo acima é de certa forma baixa, já que é mais fácil evitar a ocorrência – mas não é impossível de ocorrer. Por outro lado, em projetos maiores existe a tendência de presenciar este cenário com mais frequencia.

Para determinarmos o grau de complexidade, é importante notarmos a necessidade de múltiplas fases/módulos de projeto ao mesmo tempo. Por exemplo: um projeto simples é aquele onde temos a equipe inteira trabalhando na mesma fase; um intermediário/médio, 2 pessoas podem estar trabalhando na versão 2.0 e outras 6 dando uma manutenção na versão 1.0; já em um complexo, temos a mesma situação de uma equipe média e ainda dentro da mesma versão temos duas ou mais equipes trabalhando em módulos diferentes.

Bom com a base no raciocínio acima, vamos ao que interessa…

Projetos de pequeno porte

Projetos de pequeno porte podem ser descritos como aqueles onde a equipe é pequena, não existe a necessidade de múltiplas pessoas estarem trabalhando com o mesmo recurso do Dynamics simultaneamente e quando o projeto possui uma única versão e fase/módulo em andamento ao mesmo tempo.

Na minha opinião, existem duas maneiras de trabalhar em projetos de pequeno porte:

  • Única organização de Dynamics com múltiplas pessoas customizando/extendendo ao mesmo tempo, centralização do código fonte para a distribuição (deploy) nos demais ambientes (teste, homologação, produção)

No procedimento acima, em um único ambiente de desenvolvimento é possível ter múltiplas pessoas customizando e extendendo o Dynamics ao mesmo tempo. Já as customizações/extensões são utilizados de forma compartilhada, assim, temos as mudanças em um repositório central para que as distribuições (deploys) no demais ambientes (teste, homologação, produção) sejam extraídas.

Acredito que este seja a opção mais utilizada pela maioria dos projetos, porém ela possui uma série de problemas, quando o projeto não é de pequeno porte, tais como:

  • Os funcionais/desenvolvedores, podem sobrescrever o trabalho um do outro;
  • Entidades e atributos comummente são criados com nomes diferentes, tipo de dados diferentes, alterados e excluídos erroneamente;
  • Quando desenvolvedores estão criando/depurando seus códigos, geralmente impedem de que os demais possam utilizar o mesmo recurso, em alguns casos um ou mais processos de negócio pode ficar indisponíveis neste período ou pararem de funcionar devido a um bug introduzido no desenvolvimento;
  • Se estamos criando a documentação em tempo de projeto, será uma tarefa muito árdua de mante-la atualizada devido ao descontrole das mudanças;

Uma observação importante é que muitas pessoas pensam que apenas as extensões (web resource, plugins, workflows customizados) devem ser mantidas pelo repositório de código, assim, costumam adicionar apenas as soluções do Dynamics no repositório. Porém as customizações (entidades, atributos, processos, etc) podem e devem ser mantidas também no repositório, ferramentas com o Package Deployer e Solution Package ajudam nessa tarefa.

  • Única organização de Dynamics com múltiplas pessoas extendendo ao mesmo tempo, porém apenas uma única pessoa (gatekeeper) irá customizar (entidades, atributos, relacionamentos, processos, direitos de acesso, dashboards, views e etc). Centralização do código fonte para a deploy nos demais ambientes

Acima temos um único ambiente de desenvolvimento com múltiplas pessoas extendendo o Dynamics ao mesmo, porém apenas uma única, o gatekeeper que customiza, deste modo, não temos alguns dos problemas citados na opção anterior. As demais etapas são as mesmas da opção anterior, com o código fonte compartilhado e centralizado, sendo utilizado para a distribuições nos demais ambientes.

Assim o gatekeeper resolve o problema de múltiplas pessoas estarem customizando o Dynamics ao mesmo tempo, porém a concorrência de recursos de extensão continua sendo um problema.

Projetos de médio porte

Projetos de médio porte são aqueles em que a equipe possui mais do que cinco membros, ou equipes pequenas mas que necessitam de múltiplas pessoas trabalhando com o mesmo recurso do Dynamics simultaneamente ou ainda quando o projeto possui mais do que uma única versão ou fase/módulo em andamento ao mesmo tempo.

Assim como os projetos de pequeno porte, acredito que existam duas formas de trabalho:

  • Múltiplas organizações de Dynamics, onde cada membro da equipe possui a sua individual organização ou máquina virtual, podendo customizar/extender. O código fonte é centralizado para o deploy, porém, um gatekeeper irá consolidar todo o trabalho dos membros da equipe em um CRM central (master), realizando uma sequencia de testes para avaliar se nenhuma customizações ou extensão impede que as demais funcionem.

Quando o resultado dos testes é atendido, todas as organizações devem receber a última versão do CRM Central. Esta atividade pode ser executada de diversas formas, manualmente ou automaticamente, mas neste post não irei aprofundar neste assunto. Nesta opção é essencial o uso de Solution Packages!

Todos os problemas que não são resolvidos com o modelo para projetos de pequeno porte são solucionados com este modelo, devido a cada membro da equipe possuir seu ambiente individual. Não existe mais o problema de sobrescrever algo que outra pessoa fez ou ainda quando múltiplas pessoas precisam editar o mesmo componente.

O único ponto negativo neste cenário é a chance das customizações publicadas no repositório não funcionarem adequadamente quando promovidas para o CRM central, principalmente quando precisamos alterar o tipo de dado de um atributo, onde excluímos e recriamos com o novo tipo de dados. Se o CRM central já possuir este atributo com o tipo de dado anterior, a solução não poderá ser importada.

Deste modo, alguns tipos de controle de deploy precisam ser adicionados para garantir a qualidade das soluções, assim, quanto mais controles são adicionados, maior é tempo requerido para manter o modelo operando.

  • Múltiplas organizações de Dynamics, onde cada membro da equipe possui sua organização ou máquina virtual individual, podendo apenas extender o Dynamics. O gatekeeper é o responsável pelas atividades de customização. As demais etapas são as mesmas da opção anterior, como o código centralizado para o deploy, consolidação do trabalho pelo gatekeeper no CRM central e propagação do CRM central para todos os membros da equipe

O modelo acima é o mais preparado para alto índice de customizações e extensões, e que possui um forte sistema de consolidação e distribuição. Além disso, por termos o gatekeeper como o único responsável por realizar as customizações, as chances de problemas com a solução no repositório diminuem drasticamente, reduzindo o tempo para manter o modelo de projeto.

Projetos de grande porte

Projetos de grande porte são aqueles em que a equipe possui muitos membros (mais do que dez membros), ou equipes pequenas e intermediárias mas que necessitam de múltiplas pessoas trabalhando com o mesmo recurso do Dynamics simultaneamente ou ainda, quando o projeto possui mais do que uma versão e fase/módulo em andamento ao mesmo tempo.

Para projetos de grande porte a boa novidade é que podemos implementar o mesmo modelo de projetos de médio porte, porém para cada versão, fase ou módulo do projeto devemos criar um processo adicional.

Por exemplo, o projeto XYZ, esta com uma equipe trabalhando na versão 1.0, outra na versão 2.0 no módulo de vendas e outra equipe também na versão 2.0, mas no módulo de serviços. Assim, cada equipe terá que utilizar o mesmo modelo dos projetos de médio porte. Os projetos irão caminhar de forma independente, assim temos ramificações do projeto (branches). Devido a isso, se faz necessário múltiplos ambientes de desenvolvimento, testes, homologação.

Porém, em algum momento as diferentes frentes de projeto terão que ser consolidadas para termos uma solução final em produção, neste momento, os branches devem ser consolidados, testados em um novo CRM central e por fim aplicados nos demais ambientes e em produção.

Bom, longo post, mais muita coisa tinha que ser mencionada, espero que isso ajude nos seus projetos!

[]’s,

Tiago Cardoso

Anúncios
Publicado em Dynamics 365, Dynamics CRM | Marcado com , | Deixe um comentário

Conjunto de Opções com Multi-opções (Multi-Select Option Set)

Olá pessoal,

Um boa novidade da versão 9.0 (July Release) do Dynamics 365 é a tão esperada possibilidade de termos um conjunto de opções com mais do que uma única opção, ou seja, que permita multi-valores:

Muitos clientes aguardavam ansiosamente por isso, muitas vezes tivemos que extender o Dynamics para proporcionar a mesma funcionalidade ou algo parecido. Mas agora é algo possível. Um novo tipo de atributo chamado “Multi-Select Option Set” (assim que possível, volto com o nome oficial em português) pode ser criado nativamente, da mesma forma que criamos os conjuntos de opções (option set) anteriormente:

A visualização das opções selecionadas também foi desenvolvida, assim fica fácil para sabermos as opções utilizadas, no formulário:


Nas listas/grides:

E nos filtros:

Também podemos fazer consultas avançadas utilizando os multi-valores!

Bom é isso, uma rápida dica!

[]’s,

Tiago

Publicado em Dynamics 365 | Marcado com , , , | Deixe um comentário

Dynamics para Outlook (Outlook add-in/plug-in) não será mais suportado

Olá pessoal,

Uma das notícias mais faladas com a divulgação das funcionalidades da próxima versão do Dynamics (9.0) é o fim ao suporte ao cliente do Dynamics para Outlook (add-in ou plug-in do Dynamics para o Outlook).

Apenas para deixar claro o que estou falando. Atualmente o Dynamics conta com duas ferramentas que podemos utilizar com o Outlook:

  • Dynamics para o Outlook (add-in) – aplicativo cliente do Dynamics que instalamos para integrar o Microsoft Office Outlook com o Dynamics. Temos este aplicativo pelo menos desde a versão 4 do Dynamics (é o primeiro que vem na nossa cabeça quando falamos de Dynamics e Outlook);
  • Dynamics App para o Outlook – é uma app que adicionamos no Outlook para integrarmos ele com o Dynamics. Foi introduzida na versão 2015 do Dynamics e vem ao longo do tempo sendo incrementada com novas funcionalidades;

Vou usar o termo “add-in” para falar do Dynamics para o Outlook e “app” para o Dynamics App para o Outlook.

Após o esclarecimento acima, voltemos a notícia tema do post…

Apesar do “barulho desta notícia ser grande”, vamos a alguns fatos importantes e para esclarecer o que está por vir…

Bom, como já aconteceu anteriormente o fator de depreciar o add-in não significa que ai instalar/atualizar a nova versão do Dynamics automaticamente impedirá o funcionamento do Dynamics no Outlook (UFA!).

Podemos encarar o anuncio como uma “bandeira amarela” que acaba de ser erguida! Pois, quando uma nova atualização majoritária* ocorrer o add-in não funcionará mais.

* Entenda-se como majoritária, a atualização que costuma acontecer uma vez por ano e que realmente altera a versão do Dynamics, como por exemplo da versão 8.1.1 para 9.0.0

Agora temos uma boa notícia para quem não pretende utilizar as versões mais novas do Dynamics. Quem utilizar qualquer versão igual ou inferior a 9.0 (July 2017 Release) continuará utilizando o add-in sem problemas. Em contra partida, nenhuma nova funcionalidade será adicionada, justo não?!

Depois do que já foi dito, vamos ao porque da Microsoft está fazendo isso agora…

Três motivos impulsionaram a decisão:

  • Manutenção/Confiabilidade: Com o grande volume de novas versões e melhorias é bem mais complicado o conceito de um add-in em relação a uma App. O Outlook introduziu este conceito de apps e o Dynamics acompanhou isso criando o Dynamics App para o Outlook. Assim a manutenção dá um grande passo para a nossa atual realidade em TI;
  • Cross plataforma: O add-in apenas funciona em algumas versões do Outlook e apenas para computadores com Windows. Já a app além de funcionar no Outlook (desktop) também funciona na versão Web (Outlook Web App), Outlook para o Mac e Outlook Mobile!
  • Experiência do Usuário (UX): O add-in não apresenta as melhores formas de usabilidade, com isso a experiência do usuário é impactada. Já a app, possui um maior apelo visual e continuará incrementando isso;

Então após tudo o que eu disse, chegamos a conclusão que a app é muito melhor do que o add-in, certo? Bem… quase isso…

A app é ótima, fácil para ser atualizada e mantida, moderna, funciona em várias plataformas diferentes e etc… Porém… Existem ainda algumas funcionalidades que ainda não foram implementadas (este link está se referindo a versão 8.1 do Dynamics, ainda sem as novas funcionalidades da versão 9.0).

Sem entrar muito em detalhes, e indo direto ao ponto. A grande e única desvantagem que vejo como o “calcanhar de Aquiles” da app, é o fato de que ela não funciona OFFLINE! Sim, sabe as 99,99% das vezes que recomendamos o add-in para nossos clientes que por alguns momentos não possui acesso à internet?! A app ainda não pode ser utilizada. O argumento da Microsoft é que conseguimos o acesso offline quando utilizamos o Dynamics 365 para telefones e tablets. Sim, isso é verdade também. Porém, ainda existe uma grande quantidade de pessoas que utilizam o Dynamics apenas em seu modo desktop, com isso a opção cai novamente para o add-in. Por fim, podemos ter ainda outra maneira de manter o modo offline em um desktop/notebook, caso a versão do Windows seja superior a 8, podemos instalar a app do Dynamics e utilizá-la como como se fosse um tablet.

Enfim, por mais que exista um saída para o modo offline, ainda não temos a mesma coisa na app. É um ponto para ser pensado pelos implementadores de Dynamics…

Bom, aqui termina este post. Espero ter ajudado a esclarecer algumas dúvidas!

[]’s,

Tiago

 

Publicado em Dynamics 365, Dynamics CRM | Marcado com , | Deixe um comentário

Dynamics 365 – Documentação

Olá pessoal,

Há alguns dias atrás a Microsoft criou um novo portal para acessarmos a documentação do Dynamics 365.

A ideia foi de agrupar categorias e funcionalidades para facilitar a consulta. O conteúdo dentro de cada link não é novo, mas sim melhor organizado, otimizando nosso tempo quando estamos procurando algo!

Não se esqueça que temos todas as aplicações do Dynamics lá, assim, nosso CRM e AX estão lá com seus novos nomes, bem como todas as novas aplicações.

Para acessar é simples, clique AQUI!

[]’s,

Tiago

Publicado em Dynamics 365, Microsoft | Marcado com , | 2 Comentários

E o raio caiu no mesmo lugar! (pela 6ª vez)

Olá pessoal,

Sim, o raio caiu mais uma vez no mesmo lugar!

Pelo sexto ano seguido, fui um dos poucos felizardos e apaixonados pelo que fazem a ganhar o Microsoft Most Valuable Professional (MVP).

Acredito que ano após ano, tudo vai ficando mais difícil ao invés de facilitar algo. Este ano, posso dizer certamente isso. O reconhecimento da MS premia um ano bem diferente em minha vida profissional e pessoal. Mas, toda adversidade premia quem persevera. Posso dizer que consegui persevera por mais uma vez!

Falando da parte pessoal, por mais uma vez agradeço a minha grande companheira da vida, minha esposa Daniela. Não existe tempo ruim e dificuldade em que não possamos passar (e juntos). Obrigado mais uma vez, por me manter firme e capaz de continuar indo além!

Não posso de deixar de agradecer a todos vocês que acessam o blog e fazem dele o que ele é. Os número de acessos não param de aumentar, fico muito grato de saber que de uma forma ou outra continuo contribuindo.

Então é isso, um grande release está por fim ainda este mês, pretendo escrever sobre as novidades!

[]’s,

Tiago

Publicado em Microsoft | Marcado com , , | 1 Comentário

Dynamics 365 – Performance das Consultas

Olá pessoal,

A performance das queries (consultas) que fazemos no Dynamics CRM/365 para mim sempre foi algo que nunca cheguei a um consenso para determinar qual realmente é a mais eficaz. Principalmente quando estamos trabalhando com uma grande quantidade de dados/registros. Assim decidi, fazer alguns testes e pesquisas e escrever sobre os resultados!

Para começar este post irá abordar apenas consultas para Dynamics 365 Online. Desta forma, não estou considerando consultas à views do banco de dados (pois no Online não podemos fazer).

Ao iniciar as pesquisar achei interessante o fato de existir pouco material sobre como consultar grandes volumes de dados usando algo mais perfomático.

Encontrei sim, bastante informação sobre ExecuteMultipleRequest, mas sempre focado em Create/Update/Deletes. Por mais que seja possível de ser utilizado também para um objeto do tipo “retrieve”, o controle de paginação (page cookies) não funcionou em nenhum dos testes que fiz.

O único material que encontrei que realmente vai de encontro que o que procurava foi o PFE Core Library for Dynamics CRM. Esta biblioteca faz uso do Parallel.For para criar threads e fazer multi-chamadas. Irei falar mais disso à seguir.

Bom, o SDK do Dynamics diz que podemos fazer queries usando as seguintes técnicas/formas:

  • FetchXML
  • QueryExpression
  • LINQ (Early ou Late Bound)

Com isso, criei uma organização do Dynamics 365 Online e importei 100.000 registros de Conta(account).

No Visual Studio criei uma console application para testar cada técnica. Na minha primeira execução. Consultei apenas 10.000 registros, porém, retornando todas os atributos de cada registro. Para ter melhor número para representar a média. Repeti programaticamente a execução de cada técnica de forma isolada por 100 vezes. Com isso, acredito que temos um resultado mais confiável e não viciado.

*Apenas para ficar claro, que fiz ao máximo (dentro das minhas capacidades de programador que são muitas! rs) para que cada diferente tipo de consulta seguisse as mesmas condições, para que o resultado pudesse realmente ser comparado. Outro ponto importante, é que não existe reaproveitamento de conexões ou uso da mesma execução para testar todos os tipos de consultas de uma só vez. Cada checagem foi feita independente das demais.

Vamos ao resultado… Huuuum foi decepcionante:

Praticamente todas as técnicas demoraram em média 1 minutos para retornar 10.000 resultados! Ai devemos lembrar de nossa primeira aula de banco de dados… NUNCA FAÇA UMA CONSULTA USANDO SELECT * FROM! A própria MS não recomenda consultas sem especificar os atributos.

Sendo assim, comecei a pesquisar apenas por dois atributos (name e accountid). E repeti a mesma quantidade de dados e execuções (10.000 x 100 x técnica):

O resultado é animador! De quase 1 minuto para 4 segundos em todas as técnicas!

Após este promissor resultado, decidi retornar todos registros de conta (100.140) que eu tenho nesta organização. Porém desta fez, além das demais formas de consulta que já estava testando, adicionei mais uma. Fiz uso da biblioteca PFE que consiste no uso de Parallel.For. O número de repetições continua sendo 100. Vejam os resultados:

Boom! Estes resultados realmente mudaram minha percepção do que eu achava que era mais ou menos perfomático!!!

FetchXML – PFE mostrou o mais rápido de todos com apenas 17 segundos, foi possível recuperar 100.140 registros!

Mas até ai, não foi uma grande surpresa. Para mim, LINQ (Late Bound) ser mais rápido que QueryExpression e ainda LINQ (Early Bound) também ser mais rápido que FetchXML foram para lá de uma enorme surpresa! Eu sempre acreditei que FetchXML e QueryExpression teriam uma melhor desempenho em comparação com o LINQ, mas não foi o que os resultados mostraram.

Se formos ver ao “pé da letra”, a diferença do LINQ (Early Bound) para a QueryExpression é de 0,1 segundos, quase imperceptível. E levando em conta que LINQ certamente é a forma mais simples de  se manipular dados no mundo .NET vale muito a pena utilizá-lo sempre!

De qualquer forma, todas as técnicas mostraram-se altamente eficazes a variação entre a mais rápida (FetchXML – PFE) com a mais lenta (FetchXML) é de apenas 2,1 segundo (11 % de variação). Ainda sim, a preferência por A ou B pode ser utiliza em cenários com pouco volume de dados.

Fiz uma progressão levando em consideração estes resultados para o volume de 1 milhão de registros (claro, progressão nunca é exata, mas não custa ver). O resultado é retornado entre 2:50 à 3:10 minutos para as mesmas formas de consultas utilizadas anteriormente.

Para quem gosta de um pouco de código, segue o que utilizei para estes testes. Primeiro a Console Application:

using Microsoft.Pfe.Xrm;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace Performance.Retrieve
{
    class Program
    {
        static string queryType;
        static int count;
        static IOrganizationService orgService;

        static void Main(string[] args)
        {
            queryType = ConfigurationManager.AppSettings[0].ToString();

            StringBuilder log = new StringBuilder();
            count = Convert.ToInt32(ConfigurationManager.AppSettings[1].ToString());
            DateTime start = DateTime.Now;

            log.Append(queryType + " Retrieving " + count + " records --- ");

            switch (queryType)
            {
                case "LinqEarly":
                    LinqEarlyBound();
                    break;
                case "LinqLate":
                    LinqLateBound();
                    break;
                case "QueryExpression":
                    QueryExpression();
                    break;
                case "FetchXml":
                    FetchXml();
                    break;
                case "FetchXmlParallel":
                    FetchParallel();
                    break;
            }

            //log.AppendLine("End time: " + DateTime.Now);
            //log.AppendLine("Execution time: " + (DateTime.Now - start));
            log.Append((DateTime.Now - start).ToString());

            Console.WriteLine(log.ToString());

            Log(queryType, log);

            //Console.ReadKey();
        }

        private static void FetchXml()
        {
            // Get CRM Connection
            orgService = new CrmConnection().GetConnection();

            string fetch2 = "<fetch mapping='logical' no-lock='true'>";
            fetch2 += "<entity name='account'>";
            // fetch2 += "<all-attributes />";
            fetch2 += "<attribute name='name'/>";
            fetch2 += "<attribute name='accountid'/>";
            fetch2 += "<order attribute='accountid'/>";
            fetch2 += "</entity>";
            fetch2 += "</fetch>";

            // Define the fetch attributes.
            // Set the number of records per page to retrieve.
            int fetchCount = 5000;
            // Initialize the page number.
            int pageNumber = 1;
            // Specify the current paging cookie. For retrieving the first page, 
            // pagingCookie should be null.
            string pagingCookie = null;

            while (true)
            {
                if ((fetchCount * pageNumber) > count)
                {
                    break;
                }

                // Build fetchXml string with the placeholders.
                string xml = CreateXml(fetch2, pagingCookie, pageNumber, fetchCount);

                FetchExpression expression = new FetchExpression(xml);
                var results = orgService.RetrieveMultiple(expression);

                // * Build up results here *

                // Check for morerecords, if it returns 1.
                if (results.MoreRecords)
                {
                    // Increment the page number to retrieve the next page.
                    pageNumber++;
                    pagingCookie = results.PagingCookie;
                }
                else
                {
                    // If no more records in the result nodes, exit the loop.
                    break;
                }
            }
        }

        private static void LinqEarlyBound()
        {
            // Get CRM Connection
            orgService = new CrmConnection().GetConnection();

            // Declare Context
            using (var context = new ServiceContext(orgService))
            {
                // Retrieve Account records
                var accounts = (from m in context.AccountSet
                                orderby m.AccountId
                                select new
                                {
                                    m.Name,
                                    m.AccountId
                                }
                                )
                                .Take(count);

                var c = accounts.ToList().Count;
            }
        }

        private static void LinqLateBound()
        {
            // Get CRM Connection
            orgService = new CrmConnection().GetConnection();

            OrganizationServiceContext context = new OrganizationServiceContext(orgService);

            // Retrieve Account records
            var accounts = (from m in context.CreateQuery("account")
                            orderby m.GetAttributeValue<Guid>("accountid")
                            select new
                            {
                                name = m.GetAttributeValue<string>("name"),
                                accountid = m.GetAttributeValue<Guid>("accountid"),
                            }
                            )
                            .Take(count);

            var c = accounts.ToList().Count;
        }

        private static void QueryExpression()
        {
            // Get CRM Connection
            orgService = new CrmConnection().GetConnection();

            // Query using the paging cookie.
            // Define the paging attributes.
            // The number of records per page to retrieve.
            int queryCount = 5000;

            // Initialize the page number.
            int pageNumber = 1;

            //Create a column set.
            //ColumnSet columns = new ColumnSet(true);
            ColumnSet columns = new ColumnSet("name", "accountid");

            // Create query expression.
            QueryExpression query1 = new QueryExpression();
            query1.ColumnSet = columns;
            query1.EntityName = "account";
            //query1.TopCount = count;

            query1.AddOrder("accountid", OrderType.Ascending);

            // Assign the pageinfo properties to the query expression.
            query1.PageInfo = new PagingInfo();
            query1.PageInfo.Count = queryCount;
            query1.PageInfo.PageNumber = pageNumber;
            query1.NoLock = true;

            // The current paging cookie. When retrieving the first page, 
            // pagingCookie should be null.
            query1.PageInfo.PagingCookie = null;

            while (true)
            {
                if ((queryCount * pageNumber) > count)
                {
                    break;
                }

                // Retrieve the page.
                EntityCollection results = orgService.RetrieveMultiple(query1);

                if (results.Entities != null)
                {
                    var c = results.Entities.Count;
                }

                // Check for more records, if it returns true.
                if (results.MoreRecords)
                {
                    pageNumber++;

                    // Increment the page number to retrieve the next page.
                    query1.PageInfo.PageNumber = pageNumber;

                    // Set the paging cookie to the paging cookie returned from current results.
                    query1.PageInfo.PagingCookie = results.PagingCookie;
                }
                else
                {
                    // If no more records are in the result nodes, exit the loop.
                    break;
                }
            }
        }

        private static void FetchParallel()
        {
            var fetch = @"<fetch count='5000' no-lock='true' page='{0}'>
                                  <entity name='account'>
                                    <attribute name='name'/>
                                    <attribute name='accountid'/>
                                    <order attribute='accountid'/>
                                   </entity>
                                </fetch>";

            //<all-attributes />
            //<attribute name='name'/>
            //                        <attribute name='accountid'/>
            //                        <order attribute='accountid'/>

            IDictionary<string, QueryBase> entityQuery = new Dictionary<string, QueryBase>();
            entityQuery.Add("result", new FetchExpression(fetch));

            // CreateOnlineOrganizationServiceUrl receives two params:
            // ORG NAME - Name of your organization. If crm URL is https://tcardoso.crm6.dynamics.com it is TCARDOSO
            // CRM REGION - Use the enumerator to choose the right one
            var crmOrg = XrmServiceUriFactory.CreateOnlineOrganizationServiceUri("TCARDOSO", CrmOnlineRegion.AUSTRALIA);
            var organisationSvcManager = new OrganizationServiceManager(crmOrg, "USER", "PASSWORD");

            var queryResult = organisationSvcManager.ParallelProxy.RetrieveMultiple(entityQuery, true,
                (pair, exception) => Console.WriteLine("{0} throwed {1}", pair.Key, exception.Message));

            var c = queryResult.Values.FirstOrDefault().Entities.Count;
        }

        private static string CreateXml(string xml, string cookie, int page, int count)
        {
            StringReader stringReader = new StringReader(xml);
            XmlTextReader reader = new XmlTextReader(stringReader);

            // Load document
            XmlDocument doc = new XmlDocument();
            doc.Load(reader);

            return CreateXml(doc, cookie, page, count);
        }

        private static string CreateXml(XmlDocument doc, string cookie, int page, int count)
        {
            XmlAttributeCollection attrs = doc.DocumentElement.Attributes;

            if (cookie != null)
            {
                XmlAttribute pagingAttr = doc.CreateAttribute("paging-cookie");
                pagingAttr.Value = cookie;
                attrs.Append(pagingAttr);
            }

            XmlAttribute pageAttr = doc.CreateAttribute("page");
            pageAttr.Value = System.Convert.ToString(page);
            attrs.Append(pageAttr);

            XmlAttribute countAttr = doc.CreateAttribute("count");
            countAttr.Value = System.Convert.ToString(count);
            attrs.Append(countAttr);

            StringBuilder sb = new StringBuilder(1024);
            StringWriter stringWriter = new StringWriter(sb);

            XmlTextWriter writer = new XmlTextWriter(stringWriter);
            doc.WriteTo(writer);
            writer.Close();

            return sb.ToString();
        }

        private static void Log(string querytype, StringBuilder text)
        {
            string path = @"C:\SOME_FOLDER\" + querytype + ".txt";
            using (TextWriter tw = new StreamWriter(path, true))
            {
                // Add some information to the file.
                tw.WriteLine(text.ToString());
            }
        }
    }
}

Agora a classe de conexão (CrmConnection) com o CRM (a mesma que o SDK fornece):

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;

namespace Performance.Retrieve
{ 
    public class CrmConnection
    {
        IOrganizationService _orgService = null;

        public IOrganizationService GetConnection()
        {
            return GetConn(null);
        }

        #region Private Methods

        private IOrganizationService GetConn(string connectionString)
        {
            if (_orgService == null)
            {
               // Obtain connection configuration information for the Microsoft Dynamics
                // CRM organization web service.
                connectionString = GetServiceConfiguration();

                if (connectionString != null)
                {
                    // Connect to the CRM web service using a connection string.
                    CrmServiceClient conn = new CrmServiceClient(connectionString);

                    // Cast the proxy client to the IOrganizationService interface.
                    if (conn.OrganizationWebProxyClient != null)
                    {
                        _orgService = conn.OrganizationWebProxyClient;
                    }
                    else if (conn.OrganizationServiceProxy != null)
                    {
                        _orgService = conn.OrganizationServiceProxy;
                    }
                    else
                    {
                        if (ConfigurationManager.ConnectionStrings.Count > 2)
                        {
                            connectionString = ConfigurationManager.ConnectionStrings[2].ConnectionString;

                            // Connect to the CRM web service using a connection string.
                            conn = new CrmServiceClient(connectionString);

                            // Cast the proxy client to the IOrganizationService interface.
                            if (conn.OrganizationWebProxyClient != null)
                            {
                                _orgService = conn.OrganizationWebProxyClient;
                            }
                            else if (conn.OrganizationServiceProxy != null)
                            {
                                _orgService = conn.OrganizationServiceProxy;
                            }
                        }
                    }

                    //_orgService = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;
                }
            }

            return _orgService;
        }

        /// 
<summary>
        /// Gets web service connection information from the app.config file.
        /// If there is more than one available, the user is prompted to select
        /// the desired connection configuration by name.
        /// </summary>

        /// <returns>A string containing web service connection configuration information.</returns>
        private static string GetServiceConfiguration()
        {
            // Get available connection strings from app.config.
            int count = ConfigurationManager.ConnectionStrings.Count;

            // Create a filter list of connection strings so that we have a list of valid
            // connection strings for Microsoft Dynamics CRM only.
            List<KeyValuePair<string, string>> filteredConnectionStrings =
                new List<KeyValuePair<string, string>>();

            for (int a = 0; a < count; a++)
            {
                if (isValidConnectionString(ConfigurationManager.ConnectionStrings[a].ConnectionString))
                    filteredConnectionStrings.Add
                        (new KeyValuePair<string, string>
                            (ConfigurationManager.ConnectionStrings[a].Name,
                            ConfigurationManager.ConnectionStrings[a].ConnectionString));
            }

            // No valid connections strings found. Write out and error message.
            if (filteredConnectionStrings.Count == 0)
            {
                Console.WriteLine("An app.config file containing at least one valid Microsoft Dynamics CRM " +
                    "connection string configuration must exist in the run-time folder.");
                Console.WriteLine("\nThere are several commented out example connection strings in " +
                    "the provided app.config file. Uncomment one of them and modify the string according " +
                    "to your Microsoft Dynamics CRM installation. Then re-run the sample.");
                return null;
            }

            return filteredConnectionStrings[0].Value;
        }

        /// 
<summary>
        /// Verifies if a connection string is valid for Microsoft Dynamics CRM.
        /// </summary>

        /// <returns>True for a valid string, otherwise False.</returns>
        private static Boolean isValidConnectionString(string connectionString)
        {
            // At a minimum, a connection string must contain one of these arguments.
            if (connectionString.Contains("Url=") ||
                connectionString.Contains("Server=") ||
                connectionString.Contains("ServiceUri="))
                return true;

            return false;
        }

        #endregion Private Methods
    }
}

Por fim o Web.Config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
  </startup>
  <appSettings>
    <add key="queryType" value="FetchXml"/>
    <!--<add key="queryType" value="LinqEarly"/>
    <add key="queryType" value="LinqLate"/>
    <add key="queryType" value="QueryExpression"/>
    <add key="queryType" value="FetchXml"/>
    <add key="queryType" value="FetchXmlParallel"/>-->
    <add key="records" value="100140"/>
  </appSettings>
  <connectionStrings>
    <add name="Server=CRM Online, organization=ORG_NAME, user=USER" connectionString="Url=URL; Username=USER; Password=PASSWORD; authtype=Office365" />
  </connectionStrings>
</configuration>

Fique atendo as DLL’s que adicionei ao projeto, adicione as para a compilação!

Pronto!

Bom, por aqui termina este longo post. Espero ter ajudado com estes testes, a termos algum fundamento sobre a performance dos vários tipos diferentes de queries que podemos fazer no CRM. Tentei cobrir vários cenário de uso para que os resultados ajudem sua decisão.

[]’s,

Tiago

Publicado em Dynamics 365, Dynamics CRM | Marcado com , , , , | Deixe um comentário

Dynamics 365 – Habilite/Desabilite Plugins e Workflows Programaticamente

Olá pessoal,

Como muitos de nós já sabemos, plugins e workflows oneram um precioso tempo do CRM para serem processados. Devido a isso, por muitas vezes precisamos que eles sejam desabilitados antes de iniciarmos uma integração ou migração no CRM, certo?!

Assim, criei um console application que podemos executar todas as vezes em que desejarmos habilitar ou desabilitar nossos plugins e workflows. Minha ideia foi de criar um código exemplo para que seja utilizado da melhor forma por cada caso.

Um exemplo de uso seria criar dois serviços agendados no agendador de tarefas do Windows (Windows Task Schedule). Assim um deles desabilitariam e outro habilitariam os plugins e workflows em determinado período.

Bom vamos ao código. Primeiro a Console Application:

using System;
using System.Linq;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Sdk.Messages;

namespace ManagePluginsWorkflows
{
    class Program
    {
        static void Main(string[] args)
        {
            // *** Define if Plugins / Workflows will be ENABLED or DISABLED ***
            bool deactivate = true;

            // Get CRM Connection
            IOrganizationService orgService = new CrmConnection().GetConnection();

            // Get Organization Service (working with Lambda)
            OrganizationServiceContext context = new OrganizationServiceContext(orgService);

            // Manage Plugin's Status
            ManagePlugins(context, deactivate);

            // Manage Workflow's Status
            ManageWorkflows(context, deactivate);

            Console.ReadKey();
        }

        private static void ManagePlugins(OrganizationServiceContext context, bool deactivate)
        {
            int pluginStateCode = (deactivate ? 1 : 0); // 1 = DISABLED, 0 = ENABLED
            int pluginStatusCode = (deactivate ? 2 : 1); //  2 = DISABLED, 1 = ENABLED

            Console.WriteLine(string.Concat((deactivate ? "Deactivating" : "Activating"), " Plugins"));
            Console.WriteLine();

            // Retrieve Plugins records
            var plugins = (from m in context.CreateQuery("sdkmessageprocessingstep")
                           where m.GetAttributeValue<BooleanManagedProperty>("ishidden").Value == false // Only non internal plugins (CRM Core)
                           // If you want some specific(s) plugin(s) add here a WHERE clause!
                           select m);

            // Loop results
            foreach (var plugin in plugins)
            {
                // Get Current State Code
                int stateCode = ((OptionSetValue)plugin["statecode"]).Value;

                // Get the Name of State Code
                string stateValueName = GetPluginStateName(stateCode);

                // Check if plugin is already in the Status wanted
                if (stateCode != pluginStateCode)
                {
                    Console.Write(string.Concat("Plugin Name: ", plugin["name"], " Current Status: ", stateValueName));

                    // Set wanted State and Status code
                    plugin["statecode"] = new OptionSetValue(pluginStateCode);
                    plugin["statuscode"] = new OptionSetValue(pluginStatusCode);

                    // Update current Plugin
                    context.UpdateObject(plugin);
                    context.SaveChanges();

                    Console.Write(string.Concat(" *** Status has been Changed: ", GetPluginStateName(pluginStateCode), " ***"));
                    Console.WriteLine();
                }
                // if plugin is already set to wanted Status
                else
                {
                    Console.WriteLine(string.Concat("Plugin Name: ", plugin["name"], " Current Status is already: ", stateValueName));
                }
            }

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine(string.Concat("All Plugins have been set to ", (deactivate ? "DISABLED" : "ENABLED")));
            Console.WriteLine();
            Console.WriteLine();
        }

        private static void ManageWorkflows(OrganizationServiceContext context, bool deactivate)
        {
            int workflowStateCode = (deactivate ? 0 : 1); // 0 = DISABLED, 1 = ENABLED
            int workflowStatusCode = (deactivate ? 1 : 2); // 1 = DISABLED, 2 = ENABLED 

            Console.WriteLine(string.Concat((deactivate ? "Deactivating" : "Activating"), " Workflows"));
            Console.WriteLine();

            // Retrieve Workflow records
            var workflows = (from m in context.CreateQuery("workflow")
                             where m.GetAttributeValue<int>("type") == 1 // Only Workflows in "Definition" (1) Type
                             where m.GetAttributeValue<int>("category") == 0 // Only Workflows
                             // If you want some specific(s) workflow(s) add here a WHERE clause!
                           select m);

            // Loop results
            foreach (var workflow in workflows)
            {
                // Get Current State Code
                int stateCode = ((OptionSetValue)workflow["statecode"]).Value;

                // Get the Name of State Code
                string stateValueName = GetWorkflowStateName(stateCode);

                // Check if Workflow is already in the Status wanted
                if (stateCode != workflowStateCode)
                {
                    Console.Write(string.Concat("Workflow Name: ", workflow["name"], " Current Status: ", stateValueName));

                    SetStateRequest setStateRequest = new SetStateRequest
                    {
                        EntityMoniker = workflow.ToEntityReference(),
                        State = new OptionSetValue(workflowStateCode),
                        Status = new OptionSetValue(workflowStatusCode)
                    };
                    
                    // Update Wrokflow Status
                    context.Execute(setStateRequest);

                    Console.Write(string.Concat(" *** Status has been Changed: ", GetWorkflowStateName(workflowStateCode), " ***"));
                    Console.WriteLine();
                }
                // if Workflow is already set to wanted Status
                else
                {
                    Console.WriteLine(string.Concat("Workflow Name: ", workflow["name"], " Current Status is already: ", stateValueName));
                }
            }

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine(string.Concat("All Workflows have been set to ", (deactivate ? "DISABLED" : "ENABLED")));
            Console.WriteLine();
            Console.WriteLine();
        }

        private static string GetPluginStateName(int value)
        {
            if (value == 0)
            {
                return "Enabled";
            }
            else
            {
                return "Disabled";
            }
        }

        private static string GetWorkflowStateName(int value)
        {
            if (value == 1)
            {
                return "Enabled";
            }
            else
            {
                return "Disabled";
            }
        }
    }
}

O código não tem muito segredo, basicamente criei dois métodos um para cada processo (plugin e workflow). Dentro dos método faço um select no registros e depois habilito/desabilito de acordo com a variável “deactivate“. O resto é só história… rs

A principal parte que deve ser lembrada é a definição da variável “deactivate“:

bool deactivate = true;

Que desejamos desabilitar todos os plugins e workflows. Use o valor “TRUE“. Se queremos habilitar use o valor “FALSE“.

Agora a classe CrmConnection:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;

namespace ManagePluginsWorkflows
{
    public class CrmConnection
    {
        IOrganizationService _orgService = null;

        public IOrganizationService GetConnection()
        {
            return GetConn(null);
        }

        #region Private Methods

        private IOrganizationService GetConn(string connectionString)
        {
            if (_orgService == null)
            {
                // Obtain connection configuration information for the Microsoft Dynamics
                // CRM organization web service.
                connectionString = GetServiceConfiguration();

                if (connectionString != null)
                {
                    // Connect to the CRM web service using a connection string.
                    CrmServiceClient conn = new CrmServiceClient(connectionString);

                    // Cast the proxy client to the IOrganizationService interface.
                    if (conn.OrganizationWebProxyClient != null)
                    {
                        _orgService = conn.OrganizationWebProxyClient;
                    }
                    else if (conn.OrganizationServiceProxy != null)
                    {
                        _orgService = conn.OrganizationServiceProxy;
                    }
                }
            }

            return _orgService;
        }

        /// 


<summary>
        /// Gets web service connection information from the app.config file.
        /// If there is more than one available, the user is prompted to select
        /// the desired connection configuration by name.
        /// </summary>



        /// <returns>A string containing web service connection configuration information.</returns>
        private static string GetServiceConfiguration()
        {
            // Get available connection strings from app.config.
            int count = ConfigurationManager.ConnectionStrings.Count;

            // Create a filter list of connection strings so that we have a list of valid
            // connection strings for Microsoft Dynamics CRM only.
            List<KeyValuePair<string, string>> filteredConnectionStrings =
                new List<KeyValuePair<string, string>>();

            for (int a = 0; a < count; a++)
            {
                if (isValidConnectionString(ConfigurationManager.ConnectionStrings[a].ConnectionString))
                    filteredConnectionStrings.Add
                        (new KeyValuePair<string, string>
                            (ConfigurationManager.ConnectionStrings[a].Name,
                            ConfigurationManager.ConnectionStrings[a].ConnectionString));
            }

            // No valid connections strings found. Write out and error message.
            if (filteredConnectionStrings.Count == 0)
            {
                Console.WriteLine("An app.config file containing at least one valid Microsoft Dynamics CRM " +
                    "connection string configuration must exist in the run-time folder.");
                Console.WriteLine("\nThere are several commented out example connection strings in " +
                    "the provided app.config file. Uncomment one of them and modify the string according " +
                    "to your Microsoft Dynamics CRM installation. Then re-run the sample.");
                return null;
            }

            return filteredConnectionStrings[0].Value;
        }

        /// 


<summary>
        /// Verifies if a connection string is valid for Microsoft Dynamics CRM.
        /// </summary>



        /// <returns>True for a valid string, otherwise False.</returns>
        private static Boolean isValidConnectionString(string connectionString)
        {
            // At a minimum, a connection string must contain one of these arguments.
            if (connectionString.Contains("Url=") ||
                connectionString.Contains("Server=") ||
                connectionString.Contains("ServiceUri="))
                return true;

            return false;
        }

        #endregion Private Methods
    }
}

Essa classe é a mesma que encontramos no SDK. Apenas para criarmos a conexão com o CRM!

Por fim, o arquivo Web.Config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
  <connectionStrings>
    <add name="Server=CRM Online, organization=ORG_NAME, user=USER_NAME" connectionString="Url=URL; Username=USER_NAME; Password=PASSWORD; authtype=Office365" />
  </connectionStrings>
</configuration>

Definimos a “connection string” que nosso CRM possui!

 

Pronto! É isso!

[]’s,

Tiago

Publicado em Dynamics 365, Dynamics CRM | Marcado com , , , , , , , | Deixe um comentário

Dynamics 365 – Certificações (MCSA e MCSE)

Olá pessoal,

[Atualizado em 01/02/2018]

Como sempre cada versão nova vem com suas novas certificações! Em fevereiro deste ano (2017), foram lançadas as certificações para o Dynamics 365.

A principal novidade foi a volta da obtenção de certificações apenas quando conseguimos passar nos exames requeridos de cada certificação. Tínhamos este conceito na versão 4 do Dynamics CRM.

 

As nomenclaturas são as mesmas já utilizadas em outras tecnologias Microsoft:

  • MCSA – Nível inicial (base)
  • MCSE – Nível especialista

Deste modo, para obtermos a certificação de MCSA precisamos passar por alguns exames (provas) requeridos, assim, iremos obter a certificação. Da mesma forma que é requerido uma certificação MCSA para iniciar os exames da certificação MCSE. Afinal primeiro precisamos ter a base conhecimento para depois conseguirmos o nível de especialista!

Atualmente temos a seguintes estrutura:

MCSA: Microsoft Dynamics 365

Devemos passar nos exames abaixo:

Ao passarmos pelos exames requeridos ganhamos a certificação de MCSA Microsoft Dynamics 365.

MCSA: Microsoft Dynamics 365 for Operations

Devemos passar nos exames abaixo:

Ao passarmos pelos exames requeridos ganhamos a certificação de MCSA Microsoft Dynamics 365 for Operations.

MCSE: Business Applications

Como pre-requisito número principal, devemos ter obtido uma certificação MCSA (Microsoft Dynamics 365 ou Microsoft Dynamics 365 for Operations)

Devemos passar em ao menos UM dos exames abaixo:

Ao passarmos pelos exames requeridos ganhamos a certificação de MCSE Business Applications.

Bom, agora é se preparar e ir atrás do resultado!

[Atualizado em 01/02/2018]

[]’s,

Tiago

Publicado em Dynamics 365, Microsoft | Marcado com , , , | Deixe um comentário

Dynamics 365 – Acionar Plugin via JScript

Olá pessoal,

Uma ótima pergunta (ou conjunto de perguntas! rs) foi feita por Tiago Ferraz Alves, que seriam:

Fazer uso do objeto XMLHttpRequest (Ajax) para acionar Plugin é uma prática suportada? Quais são os ganhos e perdas? Você já utilizou anteriormente?

Achei interessante criar um post para que possa ajudar outras pessoas a tomar a decisão de utilizar ou não esta técnica.

Primeiramente, um pouco de base e dimensão do que estamos falando…

Vale apena lembrar que por mais que o Ajax seja capaz de se comunicar com o lado servidor (server-side), sua execução é feita no lado cliente (client-side) de um aplicação, pois é baseado em JScript e envio de pacotes de transmissão.

No entanto, plugins são baseados no server-side, deste modo, apenas serviços/aplicações dentro de um servidor web poderiam “trabalhar” com os plugins.

Como conclusão do que eu disse acima: não é possível “chamar” diretamente um plugin através do lado cliente. Mas indiretamente é absolutamente possível!

Por que indiretamente é possível?

Por mais que não seja possível fazer uma chamada explicita ao plugin, podemos fazer que ele seja executado usando uma chamada client-side. Para fazer isso, existem algumas formas diferentes de se fazer. Vamos a duas abordagens mais utilizadas:

Criar registro em uma entidade customizada para acionar um plugin genérico

Apesar de ainda ser um técnica que pode ser utilizada. Ela era muito mais poderosa quando ainda não tínhamos as Custom Actions (ações de um processo).

A ideia consiste em criar uma nova entidade que funcionará como um orquestrador para o plugin, ou seja, cada vez que um registro for criado neste entidade, um plugin será executado, assim, podemos capturar a ação requisitada (através do contexto e também de atributos da entidade) e realizar a lógica adequada para cada tipo de ação.

Este post não tem o objetivo de explicar tecnicamente como executar esta técnica, porém, este é ótimo artigo para que deseja.

Acionar uma Custom Action  que irá acionar um plugin

 

 

Esta técnica apenas tornou-se possível apenas a partir da versão 2013 do CRM. Faz uso de uma Ação.  Apenas lembrando o objetivo principal de uma ação, que seria, criar ações que um entidade não possui nativamente, possibilitando novos tipos de gatilhos aos processo de negócio.

Além de utilizar um recurso nativo do CRM, tem também a vantagem de necessitar menos codificação do que a técnica anterior. Temos menos etapas de desenvolvimento.

Novamente, o objetivo aqui não é demonstrar como fazer, mas ai vai um artigo com um pouco mais de sofisticação, mas faz uso das Actions!

Bom vamos as respostas das perguntas do Tiago…

– Em resposta a primeira pergunta (se é algo suportado): Sim, completamente suportado.

– Ganhos e perdas, é algo realmente relativo e depende do contexto de utilização, mas vamos lá:

  • Prós:
    • Ambas técnicas podem ser muito eficientes em casos onde precisamos acionar um plugin através de algum controle do lado cliente, como um botão do ribbon, recurso web (web resource) e etc. Pois, usando apenas plugins para esse tipo de situação, implicaria em criar um atributo qualquer que seja alterado via JScript, fazendo o formulário ser salvo e enfim executando a regra de negócio que o plugin possui. Porém, mesmo assim, teríamos que desenvolver ainda mais para fazer algo com o retorno da regra de negócio;
    • Utilizando a proposta da primeira técnica (entidade customizada orquestradora), podemos centralizar ao máximo onde as regras de negócio serão executadas;
    • Utilizando a técnica de acionar uma action, temos como grande vantagem a possibilidade de retornar um output do plugin para a action. Tudo de forma nativa! Isso é muito mais poderoso do que apenas chamar um plugin!
  • Contras:
    • Quando utilizamos chamadas cliente para acionar algo no servidor (em nosso caso plugins). Iremos criar uma chamada extra de desenvolvimento, ou seja, na verdade nossa regra de negócio está no plugin, mas estamos acionando o plugin pelo cliente. Assim, estamos resolvendo um problema, mas inserindo outro. Pois, sempre existirá a possibilidade de ter que dar manutenção nos dois lados do desenvolvimento;
    • Na primeira técnica (entidade customizada), temos o problema de centralizar demais. Pois, considere uma equipe de desenvolvimento trabalhando em um mesmo plugin sempre! Isso não irá ser fácil…
    • É importante não extrapolar o uso desta técnicas, pois para executar qualquer regra de negócio seja necessário primeiro criar um registro em uma entidade ou invocar uma action. Pense em quando processamento adicional será necessário, além de transformar o plugin em uma espécie de Web Service, se a ideia é essa, crie um Web Services! rs
    • Lembre-se também de cenários onde o CRM será consumido por outra aplicação, provavelmente a chamada para a execução de sua regra de negócio deverá ser impactada;

– Última pergunta (se já utilizei): Não, eu não utilizei, pois ao considerar os contras em relação aos pós a resposta foi o uso de outra abordagem. Porém, já criei serviços customizados para serem chamados através de um botão no ribbon por exemplo. Neste caso, não existia uma regra de negócio que precisasse ficar em um plugin.

Bom, espero ter ajudado a esclarecer algumas dúvidas!

[]’s,

Tiago

 

Publicado em Dynamics 365 | Marcado com , | 2 Comentários

The Social Media Revolution 2017

Pessoal,

Todo ano procuro este vídeo no YouTube para me atualizar em relação as Mídias Sociais e a revolução que elas veem fazendo com nossa forma de vender, divulgar e comunicar… Realmente é algo ainda pouco assimilado por nós e principalmente pelas empresas, acredito que o tema esteja sendo muito pouco explorado no Brasil.

Ai vai…

[]’s,

Tiago Cardoso

Publicado em Social Mídia | Marcado com | Deixe um comentário