sábado, 31 de mayo de 2014

Nueva herramienta de migración de configuraciones (SP1)

Con la nueva versión del SP1 de CRM 2013, se ha publicado también una nueva versión de nuestra querida SDK que puede ser descargada de aquí: http://www.microsoft.com/en-us/download/details.aspx?id=40321
Una de la herramientas que mas me gustan que la nueva versión trae es la herramienta de migración de configuraciones o “DataMigrationUtility”.
Esta herramienta permite básicamente mover entidades de configuración, son sus registros manteniendo los GUIDs de los registros.
Esta herramienta está disponible en la carpeta de la SDK “\Tools\ConfigurationMigration\DataMigrationUtility.exe”, y lo que voy a hacer a continuación en enseñar un ejemplo de funcionamiento de esta nueva herramienta con un ejemplo.
En prácticamente todos los proyectos en los que trabajo, al hacer la puesta en marcha o instalación en los entornos de Test, Pre, Integración, producción, etc. necesito mover datos de configuración en registros de CRM. Un ejemplo que enseñaré ahora será con una entidad de “Provincias”, pero también lo he probado con entidades del sistema como Unidades de negocio o Equipos, y les puedo asegurar que funciona!
Este movimiento de datos es muy importante, ya que al mantener los mismos GUIDs (códigos internos de CRM), cualquier workflow o referencia a estos registros, los seguirá manteniendo de la misma manera al ser importados.
Para el ejemplo en cuestión, voy a empezar con una entidad de Provincias como la siguiente:
image
Entonces ejecuto la aplicación “DataMigrationUtility.exe” y vamos a crear un esquema:
image
Nos conectamos al entorno de origen:
image
Se conecta:
image
Seleccionamos la entidad de Provincias y la añado:
image
Guardamos el esquema en un fichero:
image
Una vez guardado, nos pregunta si queremos exportar los datos:
image
Aquí selecciono el nombre de fichero a generar y le decimos lo cree:
image
Resultado de la exportación de datos:
image
Ahora que tenemos creados el esquema de origen con sus datos, vamos a Importar datos:
image
Nos pide conectarnos contra el entorno de destino:
image
Conectándose con el destino:
image
Seleccionamos el fichero de datos a importar:
image
Y…Sorpresa!, un error. Lo que ocurre es que en el destino no tengo creada la entidad de provincias, por lo que me da un error de esquema.
image
Después de importar las personalizaciones de la entidad de Provincias, la importación va como la seda:
image
En la importación de provincias he probado también el eliminar una provincia (me la vuelve a crear correctamente) y modificar el nombre de una provincia (me la actualiza correctamente).
También he probado mover Unidades de negocio:
image
Y equipos (ojo aquí, ya que importa también los miembros del equipo):
image
En resumen, si bien muchos de nosotros ya nos habíamos creado soluciones para realizar este tipo de movimientos de datos, ahora la SDK nos facilita la vida aportando una herramienta muy buena.
Un abrazo!

domingo, 25 de mayo de 2014

6.1.0.55 ya esta aquí! (“Leo” o “SP1”)

image

Buenas, como todos habrán escuchado, leído investigado y algunos probado, se viene el Service Pack 1 de CRM 2013 y voy a empezar a publicar contenido acerca de esto.

Lo primero, voy a aclarar unos temas, que son un poco complicados porque cambian la manera en que Microsoft y el equipo de producto de CRM, publica y maneja las Releases.

La nueva versión (Service pack 1) será la 6.1.0.55. Sí, como habréis visto, con el nuevo service pack, empiezan a contarse los Update Rollups desde cero. Por esto, ya estamos a la espera del próximo CRM 2013 SP1 Update Rollup 1 (esto no termina nunca con una nueva release, je).

Personalmente, llevo un tiempo probando y estoy encantado con los resultados y con las nuevas funcionalidades, he leído mucho contenido por ahí además de mucho contenido que ha salido del Convergence, pero imagino que muchos de ustedes nunca vieron “LEO” en español, ya que la mayor parte del contenido hasta ahora es en ingles (prácticamente todo en realidad).

Para empezar una serie de posts relacionado con la nueva release de “Primavera”, empezaré con algunos pantallazos de la nueva versión en español, que además servirá para resumir parte de lo nuevo que viene dentro de nada.

Uno de los temas mas interesantes, es que la política de actualización de versiones en CRM Online cambia. Por ejemplo antes, se planificaba tu “upgrade”, podías modificar un poco tu fecha, pero finalmente te migraban.

Ahora es distinto, ya que lo primero que hacen es actualizarte (la versión de tu CRM aumenta), pero las nuevas funcionalidades del SP1 se pueden activar manualmente cuando uno quiera accediendo aquí:

image

Para ver un resumen de todas las novedades, se puede acceder a este enlace: http://www.microsoft.com/en-us/dynamics/crm-customer-center/what-s-new.aspx

Hay una zona nueva en la configuración, donde configurar toda la funcionalidad de Servicio al cliente:

image

image

Lo nuevo de las transiciones razón para el estado:

image

image

La vuelta de la funcionalidad de detección de duplicados en la actualización de registros (incluso funciona con la creación rápida):

image

Las barras de comando ya traen 7 botones, en lugar de los 5 que había antes como máximo:

image

Otra de las cosas nuevas y muy vistosas son estos iconos que aparecen en el listado de Casos (dependiendo del campo “Origen”):

image

El detalle de la nueva botonera en Casos:

image

Bueno, intentaré entonces a partir de ahora, ir publicando algo mas de detalle y testing de todo esto nuevo.

Recomendación: empezar a moverse con todo esto, que hay mucho mucho con que “jugar”.

un saludo!

domingo, 11 de mayo de 2014

Utilizando la nueva librería Sdk.Soap.js

Avatar of Microsoft Dynamics CRM SDK Documentation Team
Hola, hoy voy a hablar acerca de la nueva librería para Dynamics CRM publicada recientemente en el Blog del equipo de Dynamics CRM: http://blogs.msdn.com/b/crm/archive/2014/04/28/new-microsoft-crm-sdk-sample-sdk-soap-js.aspx
El equipo de documentación de Dynamics CRM, lleva unos meses trabajando sobre esta solución, y la verdad que el resultado es excelente.
La idea de esta librería es ofrecer funcionalidades pre construidas y sencillas de reutilizar, para trabajar con los web services SOAP de Dynamics CRM desde javascript.
Otro de los objetivos es que la sintaxis a utilizar, sea muy similar a la que se utiliza al trabajar con la SDK desde C#, por lo tanto, gente que no conozca jQuery o no sea experto en Javascript pero que trabajen con C#, pueden trabajar con la misma.
Si entramos en profundidad en la documentación, podemos dividirla en 4 partes:
1) Sdk.Soap.js: Contiene el núcleo de esta nueva librería, que por sí sola ya permite ser utilizada.
2) Sdk.Soap.js Entity Class Generator: Para generar una clase en Javascript para trabajo con “early-bound”, similar al utilizado en C# “CRMSvcUtil.exe”. Como no trabajo con “early-bound” y sinceramente no lo recomiendo, no lo he probado.
3) Sdk.Soap.js Action Message Generator: Para la generación de “proxys” para las llamadas a las Acciones personalizadas (Custom Actions)
4) Sdk.Soap.js Samples: Ejemplos de utilización de la librería.
Ok, una vez realizada esta introducción, os voy a mostrar y compartir una solución basada en los ejemplos disponibles, pero simplificados, ya que son un poco mas complejos.
Los ejemplos que trae por defecto son muy completos, pero después hay que montarlos en CRM, por ejemplo en un WebResource, por esto, os planteo un ejemplo sencillo. Voy a crear, consultar (retrieve), actualizar y eliminar una Cuenta utilizando esta librería. Intentaré explicar paso a paso como lo he realizado y explicaré partes del código.
Primero os voy a enseñar lo que contiene mi solución y como se ejecuta:
image
Mi solución solo contiene:
  • new_Sdk.Soap.min.js: Contiene el “core” de esta solución, disponible para su descarga desde los enlaces anteriores
  • new_SdkSoapLibrary_Test.html: Es el HTML del web resource que he creado
  • new_Testing.js: Mi código javascript que realiza las llamadas CRUD para este ejemplo
Si abro el web resource HTML y lo ejecuto ve lo siguiente:
image
El HTML es muy sencillo y es el siguiente:
<html>
<head>
    <title>Ejemplo Late Binding</title>
    <script src="ClientGlobalContext.js.aspx" type="text/javascript"></script>
    <script src="new_Sdk.Soap.min.js"></script>
    <script src="new_Testing.js"></script>
    <script type="text/javascript">
        function Ejemplo() {
            document.getElementById("messages").innerHTML = "";
            writeToPage("Ejecutando Ejemplo Late Binding ");
            createAccount();
        }
    </script>
     <style>
            body {font-family: "Segoe UI";}
            table, th, td {border: solid 1px black;border-collapse: collapse;}
        </style>
</head>
<body>
    <button id="start" onclick="Ejemplo()">Ejecutar Ejemplo</button>
    <ol id="messages" />
</body>
</html>



Lo básico es la llamada a “createAccount” que es el que llama a mi código Javascript dentro de new_Testing.js.


Para el desarrollo con esta librería, recomiendo añadir en los ficheros Javascript la siguiente referencia, que permite trabajar y tener disponible el IntelliSense de toda la funcionalidad:


image


Si quiere trabajar con “entidades” con “late-bound”, dispongo de un objeto “Entity”, y es muy posible que a partir de aquí ya les empiecen a sonar temas ya que es lo casi mismo que con C#:

var account = new Sdk.Entity("account");

Si quiere crear una Cuenta con algunos datos, lo puedo hacer primero creando los datos de la entidad:

    account.addAttribute(new Sdk.String("name", name));
    account.addAttribute(new Sdk.Boolean("creditonhold", false));
    account.addAttribute(new Sdk.Double("address1_latitude", 47.638197));
    account.addAttribute(new Sdk.Double("address1_longitude", -122.131378));
    account.addAttribute(new Sdk.Int("numberofemployees", 100000));
    account.addAttribute(new Sdk.String("description", "This is a description. \n It has several lines. \n This is the third line."));
    account.addAttribute(new Sdk.Money("creditlimit", 2000000));
    account.addAttribute(new Sdk.OptionSet("accountcategorycode", 1)); //Preferred Customer

Esto es un poco “molesto”, ya que para cada tipo de dato, hayq eu añadir el “SDK.<type>” para crear los objetos. La librería nos ofrece una forma para poder simplificar esto, en donde, primero me defino los metadatos con los que trabajar, y luego mediante un método llamado “initializeEntity”, podemos trabajar de la siguiente manera:

    account.setValue("name", "Sample Account 001");
    account.setValue("creditonhold", false);
    account.setValue("address1_latitude", 47.638197);
    account.setValue("address1_longitude", -122.131378);
    account.setValue("numberofemployees", 100000);
    account.setValue("description", "This is a description. \n It has several lines. \n This is the third line.");
    account.setValue("creditlimit", 2000000.00);
    account.setValue("accountcategorycode", 1); //Preferred Customer

Es hacer lo mismo, pero de dos formas diferentes. Obviamente, la segunda parece ser algo mas limpia para mi gusto ya que trabajamos directamente con los tipos nativos de Javascript.

Luego ya la funcionalidad es sencilla, ya que una vez definidos estos objetos podemos hacer la llamadas a lo que necesitemos por ejemplo:

Create:

Sdk.Async.create(
     account,
     retrieveAccount);

Retrieve:

Sdk.Async.retrieve(
     "account",
     createdAccountId,
     getColumnSet(accountColumns),
     updateAccount );

Delete (se llama “del” ya que delete puede ser una palabra reservada de Javascript):

Sdk.Async.del("account", createdAccountId,
        deletedAccount_End);
Toda la solución, la dejo compartida como no administrada aquí.
Espero les sirva como introducción ya que la librería es muy completa, un abrazo!

sábado, 19 de abril de 2014

Funcionamiento avanzados de los sub Grids

No se si les ha ocurrido, pero por si les ocurre en algún momento, voy a explicar como funciona una funcionalidad de las que mas me gusta de Dynamics CRM 2013.

Es la funcionalidad de poder relacionar rápidamente registros “hijos” desde un formulario.

Añadir un registro existente a uno padre, consiste básicamente en cambiar ese campo “Lookup” del hijo, para relacionarlo con un nuevo registro “padre”.

Básicamente me refiero que teniendo una Cuenta por ejemplo si quiero añadirle ventas, lo que tengo que hacer es lo siguiente:

imageimage

Pero hay veces que al darle al “+” inicial, en vez de abrirnos eso, nos abre directamente el formulario de Creación rápida de la entidad (si lo tiene habilitado) o el formulario de creación principal.

Otra opción para probar es abrir el grid y ver que ocurre allí:

imageimage

Desde allí tengo la misma experiencia. La pregunta es, ¿por qué algunas relaciones se comportan así y otras nos permiten añadir de esta forma mas rápida?

La respuesta es sencilla, y depende del tipo de relación con el hijo. Si el Lookup que define la relación, lo definimos como “Requerido por la empresa”, el comportamiento cambia y esta funcionalidad deja de existir.

image

El botón de “Agregar…” desaparece, y al intentar añadir nuevos registros me abre el formulario de creación rápida:

imageimage

Por lo tanto, en este tipo de relaciones y formularios, al momento de personalizar formularios visualmente hay que tener en cuenta también si el campo es requerido o no.

Espero les sirva esto.

un saludo

domingo, 13 de abril de 2014

Nuevamente MVP y además MAP 2014!

Hola, estas dos últimas semanas, he recibido dos noticias muy motivadoras y que me hacen seguir adelante cada vez más en mis contribuciones a las comunidades de Microsoft.

En primer lugar, Microsoft me ha vuelto a nominar como MVP (Microsoft Most Valuable Professional) para el año 2014, cosa de la que me siento muy muy orgulloso  y agradezco mucho a @Microsoft y al programa @MVPAward.

Esta semana además el equipo MSDN de España, me ha nominado MAP (Microsoft Active Professional) para el año 2014. Esta es una distinción local en España, que premia a los colaboradores de las comunidades de España y otros países.

Aprovecho para comentar que mi visión es que las comunidades de Microsoft y sus colaboradores por todo el mundo ayudan a que los profesionales que utilizan tecnologías de Microsoft puedan trabajar mejor, aprender, hacer networking y mejorar los productos de esta gran empresa.

En mi experiencia personal, el colaborar en las comunidades me ha hecho conocer gente muy interesante y así ampliar mis contactos directos con profesionales que comparten mis mismas inquitudes técnicas. Por otro lado, aprendo muchísimo respondiendo consultas en los foros, publicando artículos técnicos o creando proyectos en CodePlex.

Estos tipos de programas muestran la importancia que da Microsoft a los mismos, y ayudan a que los colaboradores mas activos, fomenten la aparición de nuevos participantes.

Saludos!

Gracias Totales.

viernes, 4 de abril de 2014

Moviendo datos entre entornos diferentes

Algo que se usa mucho, y de hecho se hace en casi todos los proyectos de CRM, es que una vez que he terminado de desarrollar todo y voy a hacer la subida a los entornos de PRE, UAT, PRO, etc. hace falta también la creación de datos.
Esta creación de datos, puede provenir en general por registros de configuración que ya están creados en DES y deben ser subidos a PRO, o incluso por el simple interés de mover estos datos.
Además a veces incluso queremos que los GUIDs de los registros sean los mismos, ya que se los creo manualmente los GUIDs internos de los registros se asignan automáticamente a nuevos valores.
Esta información además debe estar sincronizada, ya que si modifico datos en el entorno de origen, luego quiero ver ese dato actualizado en el resto.
Normalmente mucha gente mueve estos datos de forma manual exportando e importando desde ficheros Excel, pero es complicado mantenerlo sincronizado y además no se puede automatizar.
Para esto, hoy he preparado una solución MUY SENCILLA que mueve los datos entre entornos.
La idea es la siguiente:
image
El código .NET no puede ser mas simple, en la llamada lo único que quiero pasar es el código son los dos accesos (origen/destino), la entidad y los campos a sincronizar:
static OrganizationService service = GetCrmService(); 
static OrganizationService service2 = GetCrmService2();
MoveData(service, service2, "account", "name,emailaddress1,telephone1");
Y el método lo he llamado “MoveData()”:


         static void MoveData(OrganizationService sourceConnection, OrganizationService destConnection,
           
string entityName, string attributeNames)
        {
           
//1) consulto todo en origen
           
string[] attributes=attributeNames.Split(",".ToCharArray());  
           
QueryExpression consulta1 = new QueryExpression(entityName);  
            consulta1.ColumnSet =
new ColumnSet(attributes);  
           
EntityCollection registros = sourceConnection.RetrieveMultiple(consulta1);  
           
foreach (Entity ent in registros.Entities)  
            {  
               
//2) Consulto si ya existe el registro en destino  
               
Entity dest = null;  
               
try 
               
{  
                    dest = destConnection.Retrieve(entityName, ent.Id,
new ColumnSet(attributes));  
                }  
               
catch (SystemException ex)  
                {  
                    dest =
null;  
                }  
               
bool existe = (dest == null) ? false : true;
  
               
if (!existe)  
                {  
                   
//3) Creacion en destino  
                   
destConnection.Create(ent);  
                   
Console.WriteLine("Registro Creado");  
                }  
               
else  
               
{  
                   
//4) Update en destino  
                   
destConnection.Update(ent);  
                   
Console.WriteLine("Registro Actualizado");  
                }  
            }  
           
Console.ReadLine();  
        }



Finalmente si lo ejecuto me da el resultado:


image

Creo que esta solución es súper fácil pero a la vez útil, para mover datos de un entorno a otro, simplemente pasando las conexiones, y los nombres de entidades y campos.

Pensar cuanto tiempo tardarías en mover 10 entidades de configuración con sus registros manualmente con Excel desde un entorno de DES a PRE, UAT y PRO, y luego intentar pensar cuanto tardarías con algo así…

Happy Developing!

Espero les sirva…

abrazo!

domingo, 16 de marzo de 2014

Consultando la auditoría de Dynamics CRM

Hoy me propongo la consulta del registro de Auditoría. La auditoría en Dynamics CRM 2013, se almacena en una especie de entidad, pero con limitaciones, y que no puede ser accesible por las búsquedas avanzadas estándar.
Solamente pueden consultarse en los propios registros o de forma general en Configuración->Auditoría->Vista resumen de auditoría:
image
image
Si necesitamos consultas algo mas avanzadas, sólo nos queda ir por los oscuros caminos de lo no soportado (atacando directamente al SQL Server), que no lo recomiendo, o sino utilizar la SDK.
En este caso, vamos a ver un ejemplo donde recojo todos los registros de auditorías de los últimos 30 días, y en caso de ser un Update o Create, además muestro el detalle de cambios actualizados.
El ejemplo de código es el siguiente:
static void GetAudit()
{
    //1) Consulta con Filtro de 30 días
    ConditionExpression cond = new ConditionExpression("createdon", ConditionOperator.LastXDays, 30);
    FilterExpression filter1 = new FilterExpression();
    filter1.AddCondition(cond);
    RetrieveMultipleRequest req = new RetrieveMultipleRequest();
    QueryExpression query = new QueryExpression("audit");
    query.ColumnSet = new ColumnSet(true); 
    query.Criteria.AddFilter(filter1);
    req.Query = query;
    RetrieveMultipleResponse res =(RetrieveMultipleResponse)service.Execute(req);

    //2) Recorremos la Auditoria devuelta
    foreach (Entity ent in res.EntityCollection.Entities)
    {
        //3) Leemos los datos del registro
        Audit record = (Audit)ent;
        string fecha = String.Format("Fecha:{0}", record.CreatedOn.Value.ToString());
        string usuario = String.Format("Usuario:{0}", record.UserId.Name);
        string accion = String.Format("Action:{0}", record.FormattedValues["action"]);
        string operacion = String.Format("Operación:{0}", record.FormattedValues["operation"]);
        string registro = String.Format("Tipo:{0} Name:{1} Guid:{2}", record.ObjectId.LogicalName, 
                record.ObjectId.Name, record.ObjectId.Id.ToString());
                
        //4) Escribimos un log de los datos
        Console.WriteLine(fecha);
        Console.WriteLine(usuario);
        Console.WriteLine(accion);
        Console.WriteLine(operacion);
        Console.WriteLine(registro);

        //5) Si es Create o Update 
        if (record.Action.Value == 1 || record.Action.Value == 2)
        {
            //6) Consultamos el detalle
            RetrieveRecordChangeHistoryRequest changeRequest = 
                new RetrieveRecordChangeHistoryRequest();
            changeRequest.Target = new EntityReference(record.ObjectId.LogicalName, 
                record.ObjectId.Id);
            RetrieveRecordChangeHistoryResponse changeResponse =
                (RetrieveRecordChangeHistoryResponse)service.Execute(changeRequest);
            AuditDetailCollection details = changeResponse.AuditDetailCollection;

            foreach (var detail in details.AuditDetails)
            {
                //7) Por cada detalle devuelto, pintamos valor anterior y actual
                Audit detail_record = (Audit)detail.AuditRecord;
                var detailType = detail.GetType();
                if (detailType == typeof(AttributeAuditDetail))
                {
                    var attributeDetail = (AttributeAuditDetail)detail;
                    foreach (KeyValuePair<String, object> attribute in 
                        attributeDetail.NewValue.Attributes)
                    {
                        String oldValue = "(no value)", newValue = "(no value)";
                        if (attributeDetail.OldValue.Contains(attribute.Key))
                            oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                        newValue = attributeDetail.NewValue[attribute.Key].ToString();
                        Console.WriteLine("Attribute: {0}, old value: {1}, new value: {2}",
                            attribute.Key, oldValue, newValue);
                    }

                    foreach (KeyValuePair<String, object> attribute in 
                        attributeDetail.OldValue.Attributes)
                    {
                        if (!attributeDetail.NewValue.Contains(attribute.Key))
                        {
                            String newValue = "(no value)";
                            String oldValue = attributeDetail.OldValue[attribute.Key].ToString();
                            Console.WriteLine("Attribute: {0}, old value: {1}, new value: {2}",
                                attribute.Key, oldValue, newValue);
                        }
                    }
                }
            }
        }
        Console.WriteLine("---------------");
    }
    Console.ReadLine();
}

En resumen, utilizo el mensaje ExecuteMultiple para la consulta general y luego para el detalle utilizo el mensaje RetrieveRecordChangeHistory.

El resultado obtenido en mi caso es el siguiente:

image

Como puede verse, tengo toda mi auditoría en un resultado ordenado, por fecha, usuario entidad y tipo de acción.

Este ejemplo es 100% funcional, tengo idea de hacer algún tipo de herramienta a futuro, que ayudarnos con los datos que hay en los registros de auditoría, para que sea mas sencillo su explotación.

Espero les sea útil este ejemplo, a mí me ha servido.