Back to Blog
6 min read

Dataverse Business Events: Event-Driven Architecture

Business events in Dataverse enable event-driven architecture patterns. When specific conditions occur in your data, events are published that external systems can subscribe to and react upon.

Business Events Overview

Business events provide:

  • Decoupled integration
  • Real-time notifications
  • Webhook delivery
  • Azure Service Bus integration

Creating Custom Business Events

Define a custom business event:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;

public class BusinessEventService
{
    private readonly IOrganizationService _service;

    public BusinessEventService(IOrganizationService service)
    {
        _service = service;
    }

    public Guid CreateCustomBusinessEvent(string name, string description)
    {
        var businessEvent = new Entity("catalogassignment");
        businessEvent["name"] = name;
        businessEvent["description"] = description;
        businessEvent["eventtype"] = 1; // Custom
        businessEvent["isactive"] = true;

        return _service.Create(businessEvent);
    }

    public void RegisterEventSchema(Guid eventId, EventSchema schema)
    {
        var schemaEntity = new Entity("catalogeventschema");
        schemaEntity["catalogassignmentid"] = new EntityReference("catalogassignment", eventId);
        schemaEntity["schemaname"] = schema.Name;
        schemaEntity["schemadefinition"] = JsonSerializer.Serialize(schema);

        _service.Create(schemaEntity);
    }
}

public class EventSchema
{
    public string Name { get; set; }
    public string Version { get; set; }
    public List<SchemaProperty> Properties { get; set; }
}

public class SchemaProperty
{
    public string Name { get; set; }
    public string Type { get; set; }
    public bool Required { get; set; }
    public string Description { get; set; }
}

Plugin to Raise Business Events

Trigger events from plugins:

public class OrderCompletedEventPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = serviceProvider.Get<IPluginExecutionContext>();
        var serviceFactory = serviceProvider.Get<IOrganizationServiceFactory>();
        var service = serviceFactory.CreateOrganizationService(context.UserId);
        var tracingService = serviceProvider.Get<ITracingService>();

        if (context.MessageName != "Update")
            return;

        var target = (Entity)context.InputParameters["Target"];
        var preImage = context.PreEntityImages["PreImage"];

        // Check if order status changed to completed
        var newStatus = target.GetAttributeValue<OptionSetValue>("statuscode")?.Value;
        var oldStatus = preImage.GetAttributeValue<OptionSetValue>("statuscode")?.Value;

        if (newStatus == 100000002 && oldStatus != 100000002) // Completed status
        {
            tracingService.Trace("Order completed, raising business event");

            // Load full order details
            var order = service.Retrieve(
                context.PrimaryEntityName,
                context.PrimaryEntityId,
                new ColumnSet(true));

            // Create event payload
            var eventPayload = new OrderCompletedEvent
            {
                OrderId = order.Id,
                OrderNumber = order.GetAttributeValue<string>("ordernumber"),
                CustomerId = order.GetAttributeValue<EntityReference>("customerid")?.Id ?? Guid.Empty,
                CustomerName = order.GetAttributeValue<EntityReference>("customerid")?.Name,
                TotalAmount = order.GetAttributeValue<Money>("totalamount")?.Value ?? 0,
                CompletedDate = DateTime.UtcNow,
                OrderLines = GetOrderLines(service, order.Id)
            };

            // Raise the business event
            RaiseBusinessEvent(service, "cr_OrderCompleted", eventPayload);
        }
    }

    private List<OrderLineEvent> GetOrderLines(IOrganizationService service, Guid orderId)
    {
        var query = new QueryExpression("salesorderdetail")
        {
            ColumnSet = new ColumnSet("productid", "quantity", "priceperunit"),
            Criteria = new FilterExpression
            {
                Conditions =
                {
                    new ConditionExpression("salesorderid", ConditionOperator.Equal, orderId)
                }
            }
        };

        var results = service.RetrieveMultiple(query);

        return results.Entities.Select(e => new OrderLineEvent
        {
            ProductId = e.GetAttributeValue<EntityReference>("productid")?.Id ?? Guid.Empty,
            ProductName = e.GetAttributeValue<EntityReference>("productid")?.Name,
            Quantity = e.GetAttributeValue<decimal>("quantity"),
            UnitPrice = e.GetAttributeValue<Money>("priceperunit")?.Value ?? 0
        }).ToList();
    }

    private void RaiseBusinessEvent(
        IOrganizationService service,
        string eventName,
        object payload)
    {
        var request = new OrganizationRequest("RaiseBusinessEvent");
        request["EventName"] = eventName;
        request["Payload"] = JsonSerializer.Serialize(payload);

        service.Execute(request);
    }
}

public class OrderCompletedEvent
{
    public Guid OrderId { get; set; }
    public string OrderNumber { get; set; }
    public Guid CustomerId { get; set; }
    public string CustomerName { get; set; }
    public decimal TotalAmount { get; set; }
    public DateTime CompletedDate { get; set; }
    public List<OrderLineEvent> OrderLines { get; set; }
}

public class OrderLineEvent
{
    public Guid ProductId { get; set; }
    public string ProductName { get; set; }
    public decimal Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

Webhook Subscription

Configure webhook endpoints:

public class WebhookSubscriptionService
{
    private readonly IOrganizationService _service;

    public WebhookSubscriptionService(IOrganizationService service)
    {
        _service = service;
    }

    public Guid CreateWebhookSubscription(
        string eventName,
        string webhookUrl,
        string authHeader)
    {
        // Create service endpoint
        var endpoint = new Entity("serviceendpoint");
        endpoint["name"] = $"Webhook - {eventName}";
        endpoint["url"] = webhookUrl;
        endpoint["contract"] = 8; // Webhook
        endpoint["authtype"] = 5; // HttpHeader
        endpoint["authvalue"] = authHeader;
        endpoint["messageformat"] = 1; // JSON

        var endpointId = _service.Create(endpoint);

        // Register step for the event
        var step = new Entity("sdkmessageprocessingstep");
        step["name"] = $"Webhook Step - {eventName}";
        step["eventhandler"] = new EntityReference("serviceendpoint", endpointId);
        step["stage"] = 40; // Post-operation
        step["mode"] = 1; // Asynchronous
        step["rank"] = 1;

        // Get SDK message ID for the business event
        var messageId = GetSdkMessageId(eventName);
        step["sdkmessageid"] = new EntityReference("sdkmessage", messageId);

        _service.Create(step);

        return endpointId;
    }

    private Guid GetSdkMessageId(string messageName)
    {
        var query = new QueryExpression("sdkmessage")
        {
            ColumnSet = new ColumnSet("sdkmessageid"),
            Criteria = new FilterExpression
            {
                Conditions =
                {
                    new ConditionExpression("name", ConditionOperator.Equal, messageName)
                }
            }
        };

        var result = _service.RetrieveMultiple(query);
        return result.Entities.FirstOrDefault()?.Id ?? Guid.Empty;
    }
}

Azure Service Bus Integration

Subscribe events to Service Bus:

public class ServiceBusSubscription
{
    private readonly IOrganizationService _service;

    public ServiceBusSubscription(IOrganizationService service)
    {
        _service = service;
    }

    public Guid CreateServiceBusSubscription(
        string eventName,
        string serviceBusNamespace,
        string queueName,
        string sasKeyName,
        string sasKey)
    {
        // Build connection string
        var connectionString = $"Endpoint=sb://{serviceBusNamespace}.servicebus.windows.net/;" +
            $"SharedAccessKeyName={sasKeyName};" +
            $"SharedAccessKey={sasKey}";

        // Create service endpoint for Service Bus
        var endpoint = new Entity("serviceendpoint");
        endpoint["name"] = $"ServiceBus - {eventName}";
        endpoint["connectionstring"] = connectionString;
        endpoint["path"] = queueName;
        endpoint["contract"] = 7; // Queue
        endpoint["messageformat"] = 1; // JSON
        endpoint["namespaceaddress"] = $"sb://{serviceBusNamespace}.servicebus.windows.net/";

        var endpointId = _service.Create(endpoint);

        // Register the step
        RegisterEventStep(eventName, endpointId);

        return endpointId;
    }

    private void RegisterEventStep(string eventName, Guid endpointId)
    {
        var step = new Entity("sdkmessageprocessingstep");
        step["name"] = $"ServiceBus Step - {eventName}";
        step["eventhandler"] = new EntityReference("serviceendpoint", endpointId);
        step["stage"] = 40;
        step["mode"] = 1;
        step["rank"] = 1;

        var messageId = GetSdkMessageId(eventName);
        step["sdkmessageid"] = new EntityReference("sdkmessage", messageId);

        _service.Create(step);
    }

    private Guid GetSdkMessageId(string messageName)
    {
        // Implementation
        return Guid.Empty;
    }
}

Consuming Events in Azure Functions

Process business events:

public class BusinessEventProcessor
{
    private readonly ILogger<BusinessEventProcessor> _logger;

    public BusinessEventProcessor(ILogger<BusinessEventProcessor> logger)
    {
        _logger = logger;
    }

    [Function("ProcessOrderCompleted")]
    public async Task ProcessOrderCompletedAsync(
        [ServiceBusTrigger("order-completed", Connection = "ServiceBusConnection")]
        ServiceBusReceivedMessage message)
    {
        var eventData = JsonSerializer.Deserialize<DataverseBusinessEvent>(
            message.Body.ToString());

        _logger.LogInformation(
            "Processing OrderCompleted event for order {OrderId}",
            eventData.PrimaryEntityId);

        var orderEvent = JsonSerializer.Deserialize<OrderCompletedEvent>(
            eventData.Payload);

        // Process the event
        await UpdateInventoryAsync(orderEvent);
        await SendConfirmationEmailAsync(orderEvent);
        await UpdateAnalyticsAsync(orderEvent);

        _logger.LogInformation(
            "Completed processing order {OrderNumber}",
            orderEvent.OrderNumber);
    }

    private async Task UpdateInventoryAsync(OrderCompletedEvent order)
    {
        foreach (var line in order.OrderLines)
        {
            _logger.LogInformation(
                "Updating inventory for product {ProductId}, quantity: {Quantity}",
                line.ProductId, line.Quantity);
            // Update inventory system
        }
    }

    private async Task SendConfirmationEmailAsync(OrderCompletedEvent order)
    {
        _logger.LogInformation(
            "Sending confirmation email for order {OrderNumber}",
            order.OrderNumber);
        // Send email via SendGrid, etc.
    }

    private async Task UpdateAnalyticsAsync(OrderCompletedEvent order)
    {
        _logger.LogInformation(
            "Recording analytics for order {OrderNumber}",
            order.OrderNumber);
        // Update analytics/reporting system
    }
}

public class DataverseBusinessEvent
{
    public string EventName { get; set; }
    public Guid PrimaryEntityId { get; set; }
    public string PrimaryEntityName { get; set; }
    public string Payload { get; set; }
    public DateTime Timestamp { get; set; }
}

Summary

Dataverse business events enable:

  • Event-driven integration patterns
  • Webhook and Service Bus delivery
  • Custom event definitions
  • Decoupled system architecture
  • Real-time data synchronization

Build reactive systems that respond to business changes instantly.


References:

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.