SDK Rest Retrieve con Expand

La librería SDK.REST es la mejor herramienta que tenemos para hacer queries a CRM desde Javascript. Esta librería ofrece una serie de métodos con los cuales podemos interactuar con la Api REST de CRM. Uno de esos métodos es el Retrieve que incluye el parámetro Expand que vamos a analizar.

Con el método Retrieve podemos obtener un registro de CRM indicándole la entidad y el Id del mismo. Además es siempre recomendable que le indiquemos los campos que necesitamos puesto que por defecto te devolverá todos, cosa que en la mayoría de los casos no necesitaremos y además ahorraremos recursos.

La definición del método RetrieveRecord según la librería es el siguiente:

El primer y el segundo parámetro son el Id y entidad del registro que queremos recuperar. Es importante notar que el nombre de la entidad es el Nombre de Esquema (Schema name) que se escribe con mayúsculas/minúsculas según está definido en la solución.

El tercer parámetro son los campos que queremos recuperar. En este caso sería una cadena de texto con los nombres (de esquema) de los campos que queremos obtener, separados por comas.

Las dos ultimas, son las funciones Callback de Exito/Error que se ejecutarán si el registro se ha devuelto bien o si se ha encontrado algún error respectivamente.

Finalmente, como cuarto parámetro tenemos el Expand. El Expand es una cadena de texto donde se incluirán los nombres de las relaciones que queremos expandir siempre en referencia al registro que estamos pidiendo. Se podrán incluir relaciones 1:N y N:1. En cada caso obtendremos un objeto distinto como resultado.

Relaciones 1:N de la entidad Account

Relaciones 1:N de la entidad Account

Es interesante recalcar que con los Expands no se indican qué campos de la entidad expandida quieres, sino que te devuelve todos. Es por esto que aunque en la definición de la librería diga que se pueden hasta expandir 6 relaciones, en mi opinión no es nada recomendable puesto que te devolverá en una sola llamada un volumen muy grande de datos [ 1 registro padre + (N1 registros hijos relación 1 * M1 campos cada registro)  + (N2 registros hijos relación 2 * M2 campos cada registro) + …  ]. Si multiplicamos esto por cada usuario de CRM que esté trabajando podemos sobrecargar el sistema.

Expand con relación 1:N

Un ejemplo sería obtener una cuenta y además todos los contactos asociados a la cuenta. Como es lógico, cuando hacemos esto obtenemos 1 resultado padre, la cuenta, y N hijos, todos los contactos relacionados con esa cuenta. La petición y deserialización sería del siguiente modo:

Como vemos en la siguiente imagen, el objeto response que nos devuelve como parámetro en el SuccessCallBack tiene primero los campos pedidos en el Select del registro padre (cuenta) y además, colgando del objeto contact_customer_accounts.results tenemos un Array con cada uno de los resultados hijos (contactos) y todos sus atributos.

Estructura del objeto response en un Retrieve con Expand 1:N

Estructura del objeto response en un Retrieve con Expand 1:N

Para recorrer el array de respuesta lo haremos como en se muestra en el código superior, con un bucle For que recorra todos los results de la expansión.

Expand con relación N:1

Ahora, en el caso opuesto, vamos a expandir una relación N:1. Esto es, queremos el padre del registro que estamos solicitando. Como no puede ser de otra manera, en este caso solo esperamos que nos devuelva un resultado y no N como en el ejemplo anterior.

Siguiendo con el ejemplo anterior, vamos a expandir la relación de “Contacto principal” de una Cuenta.

El código quedaría algo del estilo:

En este caso, al disponer de un solo resultado en la expansión, se ahorra el results del objeto account_primary_contact dentro del objeto response.

Estructura del objeto response en un Retrieve con Expand N:1

Estructura del objeto response en un Retrieve con Expand N:1

Expand con relación N:N

Desconozco si es posible expandir una relación N:N pero todas las pruebas que he hecho no han funcionado. Si alguien sabe cómo hacerlo le agradecería que indicase cómo en los comentarios!

Mezclando expansiones

Como hemos indicado con anterioridad, es totalmente posible expandir varias relaciones en la misma petición (hasta 6) y por cada una de ellas obtendremos un objeto colgando del response con el nombre de la relación. Si es 1:N además éste contendrá un Array denominado results con todos los hijos del registro.


Para terminar, indicar con la extensión para Chrome HUDCRM, en la pestaña “Query constructor” podéis obtener todos estos códigos mostrados en el post de manera muy sencilla.

Ejemplo constructor de queries SDK.REST con la extensión para Chrome HUDCRM

Ejemplo constructor de queries SDK.REST con la extensión para Chrome HUDCRM

 

Relacion entre datos cargados y entidad ImportFile

La funcionalidad que dispone CRM para importar archivos es bastante atractiva para los clientes que compran el producto. El poder cargar de modo automático grandes archivos de datos en la base de datos de CRM es una funcionalidad que se valora, sobre todo cuando el CRM es un sistema complementario a otros como podría ser un SAP, para tener los entornos alineados.

Lo único que es necesario configurar para automatizar la tarea de importación es un mapping de datos. Este mapping se tendrá que hacer con el primer archivo que se cargue y en él se indicará qué columnas del archivo Excel se cargarán en qué campos de la entidad de CRM. Posteriormente, el usuario de CRM únicamente tendrá que seleccionar el archivo que quiera importar y el mapping que se creó relativo a ese archivo. Como punto negativo únicamente decir que CRM admite de modo nativo importaciones de archivos con extensión CSV. Desde el propio Excel es relativamente sencillo exportar nuestro archivo con extensión .XLSX/.XLS a un archivo .CSV.

Desde el menú de Excel Guardar como podremos seleccionar la opción “CSV (delimitado por comas)” que nos generará el archivo con la estructura adecuada para Dynamics.

Exportar Excel como CSV

Exportar Excel como CSV

Para quien le interese el tema, si abrimos el archivo CSV con un editor de texto plano nos daremos cuenta que la estructura del propio archivo es muy simple, siendo la primera línea el nombre de las columnas separadas por punto y coma, y el resto los valores para cada columna, separados con el mismo carácter. Con esta estructura, no sería muy complicado escribir un programa que generase archivos CSV de cualquier base de datos, listos para importar en nuestro Dynamics.

Volviendo a la importación, una vez seleccionado el archivo y el mapping, el CRM empezará a cargar los datos. Nuestro trabajo terminaría aquí si únicamente nos interesase cargar los datos sin ningún tipo de procesado.
No obstante, no son pocos los clientes que se interesan por no solo cargar datos, sino también aplicarle algún tipo de lógica. Me viene a la mente por ejemplo una lógica de sustitución de un código cargado en el archivo por otro que esté ya en la base de clientes de CRM. O sencillamente relacionar cada registro cargado con un lookup a otra entidad mediante algún campo de identificación.

La idea de este post no es proponer una arquitectura de workflow o programa que aplique esta lógica puesto que en función de la cantidad de datos cargados se elegirá una u otra. El propósito de este post es descubrir de qué modo quedan relacionadas cada una de los registros creados en nuestra entidad con el archivo cargado.

Partimos por tanto de un dato conocido: el archivo cargado en la entidad ImportFile así como todos los datos asociados a él (número de líneas procesadas, fecha de inicio de carga, fecha de fin de carga, etc). Para complicarlo vamos a ponernos la restricción de que todos los archivos cargados van a la misma entidad. Sea cual sea el tipo de archivo se cargará en una entidad personalizada para la carga, escribiendo solo en unos campos en función del maping. El objetivo es conociendo únicamente el archivo de carga, obtener todos los registros creados en CRM a partir de él.

Logical name Schema name Label Type Descripción
importfileid ImportField Import Uniqueidentifier Id unico del registro
createdby CreatedBy Created By Lookup Usuario que cargo el file
completedon CompletedOn Completed On DateTime Fecha en la que se completó la importación
createdon CreatedOn Created On DateTime Fecha en la que se creó el registro
failurecount FailureCount Errors Integer Numero de lineas con error
partialfailurecount PartialFailureCount Partial Failures Integer Numero de lineas con error parcial
successcount SuccessCount Successes Integer Numero de lineas importadas correctamente
totalcount TotalCount Total Processed Integer Total de lineas (debería ser igual a la suma de los tres valores anteriores)
source Source Source String Nombre del file fuente
name Name Import Name String Nombre del registro en CRM. Coincide con el source
targetentityname TargetEntityName Target Entity String Entidad donde se cargarán los datos (en el ejemplo new_uploadentity)

Campos de la entidad ImportFile de CRM

Una primera idea sería obtener las fechas de inicio y final de carga del archivo y realizar un Retrieve Multiple de datos creados en ese intervalo de tiempo en nuestra entidad. Esta solución tiene un problema que es que funcionará siempre y cuando no se carguen dos –o más- archivos al mismo tiempo. Como lado positivo, esta es una query rápida que no requiere de joins y que si no mezclamos archivos al mismo tiempo obtiene buenos resultados.

Pero los usuarios no entienden de restricciones y si existe la posibilidad de importar files, seguro que más de una vez nos encontramos con que se solapan cargas. Para solucionar esto vamos a realizar una búsqueda de registros insertados en nuestra entidad partiendo del ID del archivo cargado.

Dynamics ha realizado un gran trabajo en este aspecto implementando un método de carga en paralelo a dos entidades. Por cada línea de nuestro archivo CSV se cargarán en CRM dos registros. El primero será siempre el registro que contiene la información de nuestros datos del Excel con el mapping aplicado en nuestra entidad de carga. Esta sería la importación misma. El segundo registro se almacena en la entidad ImportData. Este segundo registro no incluye información del archivo sino más bien información de la línea en bruto. Algunos datos que podemos encontrar en esta entidad son la posición de la línea en el file, los datos sin procesar de la línea o el ID de la línea en nuestra entidad de carga. Además en la entidad ImportData existe un campo lookup a la entidad ImportFile. El esquema de entidades sería el siguiente:

Relación entre entidad ImportFile, entidad ImportData y nuestra entidad de carga

Relación entre entidad ImportFile, entidad ImportData y nuestra entidad de carga

Como vemos, cuando cargamos un file se crea un registro en la entidad ImportFile que contiene la información de cabecera del file. En el proceso de importación, por cada línea del Excel se creará un registro en la entidad ImportData y otro registro en nuestra entidad de carga, en el esquema new_uploadentity.  El modo en que quedan relacionadas son que en el campo recordid del registro creado en ImportData  se incluye el id único del registro new_uploadentityId.

Ya tenemos todo para realizar la query y obtener los resultados. Las dos opciones que propongo son QueryExpression y Linq. Por mi parte recomiendo Linq puesto que no tiene la limitación de los 5000 registros recibidos.

En ambos casos la función nos devolverá los datos cargados en nuestra entidad new_UploadEntity dado el ID de un file de la entidad ImportFile.

En QueryExpression el código será similar a:

Si utilizamos Linq el código sería

En ambos casos he utilizado una clase de definición como la siguiente: