1.3 Mensajes y Entidades - Eventos del Sistema

Conoce los mensajes del sistema y cómo trabajar con entidades

Cada vez que alguien interactúa con Dataverse, ya sea creando un registro, actualizándolo, eliminándolo, o cualquier otra operación, se dispara un mensaje. Entender qué mensajes existen, qué información contienen, y cómo acceder a ella es fundamental para escribir plugins que funcionen correctamente.

Objetivos de aprendizaje

  • Conocer los mensajes más importantes del sistema y cuándo se disparan
  • Entender cómo acceder al Target y qué contiene en cada mensaje
  • Dominar el uso de InputParameters y OutputParameters
  • Saber qué información está disponible en cada tipo de operación

El concepto de mensaje en Dataverse

Cuando pensamos en bases de datos tradicionales, hablamos de operaciones CRUD: Create, Read, Update, Delete. Dataverse toma este concepto y lo expande. Cada operación que puedes realizar en el sistema tiene un nombre, y ese nombre es el mensaje.

Los mensajes más obvios son los CRUD: Create, Retrieve, Update, Delete. Pero hay docenas más. Assign cambia el propietario de un registro. SetState cambia su estado. Associate crea una relación entre dos registros. Win cierra una oportunidad como ganada. Y la lista continúa.

Cuando registras un plugin, le dices a Dataverse: "cuando se dispare este mensaje para esta entidad, ejecuta mi código". Así que la primera pregunta que debes responder al diseñar un plugin es: ¿qué mensaje necesito interceptar?

Los mensajes CRUD: la base de todo

El 90% de los plugins que escribirás se registrarán en uno de estos cuatro mensajes:

Create se dispara cuando se crea un nuevo registro. Esto incluye creaciones desde formularios, importaciones, API, flujos de Power Automate, y cualquier otra fuente. Lo único que lo dispara es la creación de un registro nuevo.

Update se dispara cuando se modifica un registro existente. Aquí hay un detalle que muchos desarrolladores no entienden al principio: no importa cuántos campos cambies, solo hay un mensaje Update por operación de guardado.

Delete se dispara cuando se elimina un registro. Esto incluye eliminaciones directas y eliminaciones en cascada (cuando se elimina un registro padre que arrastra a sus hijos).

Retrieve y RetrieveMultiple se disparan cuando se leen datos. Retrieve para obtener un solo registro, RetrieveMultiple para consultas que devuelven varios. Estos son menos comunes para plugins, pero útiles para escenarios como ocultar datos sensibles o añadir campos calculados dinámicamente.


El Target: donde viven los datos de la operación

Cuando tu plugin se ejecuta, necesitas acceder a los datos de la operación. ¿Qué registro se está creando? ¿Qué campos se están modificando? ¿Qué registro se va a eliminar? Esta información está en el Target.

El Target es un parámetro de entrada que el sistema pasa a cada operación. Dependiendo del mensaje, el Target puede ser un objeto Entity completo o solo una referencia EntityReference.

Create: el Target contiene el registro completo

En una operación de Create, el Target es un objeto Entity que contiene todos los campos que el usuario ha rellenado. Si María crea un contacto con nombre "Juan García" y email "juan@ejemplo.com", el Target contendrá esos dos campos.

Un detalle importante: en Pre-operation de un Create, el campo Id del Target está vacío (Guid.Empty). Dataverse todavía no ha asignado el identificador. En Post-operation, el Id ya tiene su valor.


// En un plugin de Create
Entity target = (Entity)context.InputParameters["Target"];

// Acceder a campos
string nombre = target.GetAttributeValue("fullname");
string email = target.GetAttributeValue("emailaddress1");

// En Pre-operation, esto es Guid.Empty en Create
Guid id = target.Id;

Update: el Target solo contiene lo que cambió

Este es uno de los conceptos más importantes y fuente de muchos errores. En una operación de Update, el Target NO contiene todos los campos del registro. Solo contiene los campos que se están modificando.

Imagina que Laura abre un contacto que tiene 20 campos rellenados y modifica solo el teléfono. El Target contendrá únicamente el campo teléfono y el Id del registro. Los otros 19 campos no están.

Esto tiene implicaciones para tu código. Si necesitas el valor de un campo que no está siendo modificado, no puedes obtenerlo del Target. Necesitas usar un Pre-Image (que veremos en detalle más adelante) o hacer una consulta adicional.


// Error común: asumir que el campo existe
// Si el usuario no modificó "creditlimit", esto devuelve null
Money creditLimit = target.GetAttributeValue("creditlimit");

// Forma correcta: verificar primero
if (target.Contains("creditlimit"))
{
    Money creditLimit = target.GetAttributeValue("creditlimit");
    // El campo está siendo modificado, procesar...
}
Error que veo constantemente: Plugins que fallan porque asumen que un campo está en el Target cuando no está siendo modificado. Siempre verifica con Contains() antes de acceder a campos en Update.

Delete: el Target es solo una referencia

En operaciones de Delete, el Target no es un Entity completo sino un EntityReference. Solo contiene el LogicalName de la entidad y el Id del registro que se va a eliminar.

Si necesitas información sobre el registro antes de que se elimine, debes usar un Pre-Image. En Post-operation de Delete, el registro ya no existe, así que un Pre-Image es la única forma de acceder a sus datos.


// En un plugin de Delete
EntityReference target = (EntityReference)context.InputParameters["Target"];

string entityType = target.LogicalName;  // "contact"
Guid recordId = target.Id;               // El GUID del contacto que se elimina

// El Target NO tiene los datos del registro
// Necesitas Pre-Image para acceder a campos

Mensajes especializados que debes conocer

Más allá del CRUD básico, hay mensajes para operaciones específicas. Saber que existen te permite registrar plugins más precisos.

Assign: cambio de propietario

Cuando se cambia el propietario de un registro, se dispara Assign. No es lo mismo que un Update del campo ownerid. El mensaje Assign te permite interceptar específicamente los cambios de propietario.

Esto es útil para lógica como: "cuando se reasigna una cuenta, verificar que el nuevo propietario tiene las certificaciones necesarias para atender clientes de esa industria".

SetState y SetStateDynamicEntity: cambios de estado

Cuando un registro cambia de estado activo a inactivo (o viceversa), se disparan estos mensajes. Dataverse gestiona los estados de forma especial, separados de los campos normales.

Por ejemplo, puedes tener un plugin que se dispare solo cuando alguien desactiva una cuenta, para verificar que no hay oportunidades abiertas antes de permitir la desactivación.

Associate y Disassociate: relaciones muchos a muchos

Cuando se crea o elimina una relación muchos a muchos entre registros, se disparan estos mensajes. No son muy intuitivos al principio.

Imagina que tienes una entidad Proyecto y una entidad Recurso, relacionadas muchos a muchos. Cuando asignas un recurso a un proyecto, no hay Create de ningún registro (la tabla intermedia se gestiona automáticamente). Lo que sí hay es un mensaje Associate.

Los parámetros son diferentes a los CRUD normales:


// En un plugin de Associate
EntityReference target = (EntityReference)context.InputParameters["Target"];
EntityReferenceCollection relatedEntities = 
    (EntityReferenceCollection)context.InputParameters["RelatedEntities"];
Relationship relationship = 
    (Relationship)context.InputParameters["Relationship"];

// "Target" es uno de los registros
// "RelatedEntities" es la colección de registros que se están asociando
// "Relationship" identifica qué relación se está usando

Mensajes específicos de entidades

Algunas entidades tienen mensajes propios. La oportunidad tiene Win y Lose para cerrarla como ganada o perdida. El caso tiene Close. Las citas recurrentes tienen BookRecurringAppointment.

Usar estos mensajes específicos es más preciso que intentar detectar la operación a través de un Update genérico. Por ejemplo, detectar que una oportunidad se ganó mirando si el statecode cambió a "Won" es más frágil que simplemente registrarte en el mensaje Win.


Filtering Attributes: optimizando plugins de Update

Los plugins de Update tienen una característica que puede salvarte de problemas de rendimiento: los Filtering Attributes.

Imagina que tienes un plugin que recalcula el crédito disponible de un cliente cuando cambia su límite de crédito. Si registras el plugin en Update de Account sin más, se ejecutará cada vez que alguien modifique cualquier campo de cualquier cuenta. Miles de ejecuciones innecesarias.

Filtering Attributes te permite decir: "ejecuta este plugin SOLO cuando se modifique el campo creditlimit". Si alguien modifica el teléfono o la dirección, el plugin no se dispara.

Esto se configura al registrar el plugin en el Plugin Registration Tool. No es código, es configuración de registro.

Buena práctica: Siempre que registres un plugin en Update, pregúntate: ¿qué campos realmente me interesan? Define Filtering Attributes para esos campos. Tu sistema te lo agradecerá.

InputParameters y OutputParameters: más allá del Target

El Target es el parámetro de entrada más común, pero no es el único. Cada mensaje tiene su conjunto de InputParameters y OutputParameters que definen qué información va hacia la operación y qué sale de ella.

InputParameters: lo que entra

Para la mayoría de mensajes CRUD, el InputParameter principal es Target. Pero hay otros:

En Retrieve, además del Target (que es un EntityReference indicando qué registro leer), tienes ColumnSet que indica qué columnas se solicitan.

En RetrieveMultiple, no hay Target. En su lugar tienes Query, que puede ser un QueryExpression, FetchExpression, o QueryByAttribute.


// Ejemplo: plugin en RetrieveMultiple para filtrar resultados
if (context.InputParameters.Contains("Query"))
{
    QueryExpression query = context.InputParameters["Query"] as QueryExpression;
    if (query != null)
    {
        // Puedes modificar la query antes de que se ejecute
        // Por ejemplo, añadir filtros adicionales
    }
}

OutputParameters: lo que sale

Algunos mensajes producen salida que puedes interceptar. El más útil es en Create: después de crear un registro, el OutputParameter "id" contiene el identificador del nuevo registro.

En Retrieve y RetrieveMultiple, los resultados están en OutputParameters. Puedes modificarlos en Post-operation para añadir campos calculados o filtrar información sensible.


// En Post-operation de Retrieve
if (context.OutputParameters.Contains("BusinessEntity"))
{
    Entity retrieved = (Entity)context.OutputParameters["BusinessEntity"];
    // Puedes añadir campos calculados que no existen en la BD
    retrieved["campo_calculado"] = "Valor dinámico";
}

// En Post-operation de RetrieveMultiple
if (context.OutputParameters.Contains("BusinessEntityCollection"))
{
    EntityCollection results = 
        (EntityCollection)context.OutputParameters["BusinessEntityCollection"];
    // Puedes filtrar registros o añadir datos a cada uno
}

Referencia práctica: qué esperar de cada mensaje

Después de trabajar con decenas de plugins, estas son las características que necesitas recordar para cada mensaje común:

Create: Target es Entity con campos nuevos. Id está vacío en Pre-op, disponible en Post-op. No hay Pre-Image (el registro no existe todavía). Post-Image disponible en Post-op.

Update: Target es Entity con SOLO campos modificados e Id. Pre-Image disponible para ver valores anteriores. Post-Image disponible en Post-op para ver estado final. Usa Filtering Attributes.

Delete: Target es EntityReference (solo LogicalName e Id). Pre-Image disponible para ver datos antes de eliminar. No hay Post-Image (el registro ya no existe).

Retrieve: Target es EntityReference. ColumnSet en InputParameters. Resultado en OutputParameters["BusinessEntity"]. Solo Post-op tiene sentido para modificar resultados.

RetrieveMultiple: Query en InputParameters. Resultados en OutputParameters["BusinessEntityCollection"]. Útil para inyectar filtros o modificar resultados.


Puntos clave

  • Cada operación en Dataverse dispara un mensaje específico (Create, Update, Delete, Assign, etc.)
  • El Target contiene los datos de la operación: Entity completa en Create, solo campos modificados en Update, EntityReference en Delete
  • En Update, siempre verifica si un campo existe antes de acceder a él con Contains()
  • Usa Filtering Attributes en plugins de Update para ejecutarlos solo cuando cambien campos específicos
  • InputParameters y OutputParameters contienen información adicional según el mensaje
  • Mensajes especializados como Assign, SetState, Associate permiten lógica más precisa

Para profundizar

Inicia sesión e inscríbete para guardar tu progreso.
En este curso
¿Te ha resultado útil?