Dataverse Plug-in Execution Pipeline Laboratory: Stages, Images, Transactions, and C# Diagnostics
Engineering server-side Dataverse behavior through the event execution pipeline, synchronous and asynchronous plug-ins, execution context, entity images, tracing, recursion control, and reproducible failure injection.
Power Platform Engineering Academy — Five Years in Five Days
Application: APP-011 — Plug-in Execution Pipeline Laboratory
Technical domain: Dataverse server-side extensibility
Technologies: C#, .NET, Microsoft.Xrm.Sdk, Plug-in Registration Tool, Dataverse Event Framework, Plugin Trace Log, Model-Driven Apps
Status: Proposed engineering design — implementation and runtime evidence pending
Introduction
A Dataverse plug-in is not simply a C# event handler.
It executes inside a platform-managed event pipeline, where the meaning of an operation depends on its message, stage, execution mode, transaction context, registered images, and the identity under which it executes.
A plug-in registered on Update can run before the database operation, after the operation, synchronously within the transaction, or asynchronously after the originating transaction has completed.
Those choices affect correctness, performance, rollback, user experience, and error handling.
Consider a record whose amount changes from 1,000 to 1,500.
A PreOperation plug-in can validate or modify the pending update before persistence. A synchronous PostOperation plug-in can create related audit records inside the transaction. An asynchronous PostOperation plug-in can perform follow-up processing without blocking the original user request.
These operations are not architecturally equivalent.
APP-011 — Plug-in Execution Pipeline Laboratory investigates the complete execution lifecycle through a reusable C# plug-in, controlled update experiments, transaction diagnostics, and a functional Model-Driven App.
The central engineering question is:
How do plug-in stage, execution mode, context, and registration configuration determine the behavior of server-side Dataverse operations?
1. The Dataverse Event Framework
Dataverse exposes an event processing pipeline for supported messages.
Common messages include:
CreateUpdateDeleteAssociateDisassociate- Custom API messages
A plug-in step is registered for a particular message, primary table where applicable, pipeline stage, and execution mode.
The platform invokes the plug-in when an operation matches that registration.
The same plug-in assembly can contain multiple classes, and the same class can be registered in multiple steps.
This makes registration metadata part of the effective application behavior.
A correct C# implementation with an incorrect step registration can produce entirely different results from those intended.
Reference: Event Framework.
2. Execution Pipeline Stages
The principal stages are:
| Stage | Numeric value | Purpose |
|---|---|---|
| PreValidation | 10 | Validate before the main operation |
| PreOperation | 20 | Modify or validate data before the core operation |
| MainOperation | 30 | Platform operation; not a normal custom plug-in registration stage |
| PostOperation | 40 | React after the core operation |
A simplified execution sequence is:
Incoming Dataverse Request │ ▼ PreValidation (10) │ ▼ PreOperation (20) │ ▼ MainOperation (30) │ ▼ PostOperation (40) │ ▼ Response
This diagram represents the synchronous pipeline conceptually.
Asynchronous PostOperation processing follows a separate execution path after the originating operation is committed.
2.1 PreValidation
PreValidation is typically used for early validation.
For a top-level request, it commonly executes before the main database transaction.
However, nested requests initiated from an existing transaction can cause PreValidation to execute while a transaction is already active.
Therefore, it is incorrect to assume that PreValidation is always outside a transaction.
The runtime property IPluginExecutionContext.IsInTransaction provides the relevant observation.
2.2 PreOperation
PreOperation executes before the platform’s main operation and within the transaction for applicable synchronous operations.
It is generally the preferred stage for modifying the pending Target attributes.
For example:
target["pea_normalizedname"] = normalizedValue;
This allows the platform to persist the modified attributes as part of the original operation.
Calling service.Update on the same record from its own PreOperation Update step is generally unnecessary and can introduce additional pipeline execution.
2.3 PostOperation
Synchronous PostOperation executes after the main operation but before the enclosing transaction completes.
It is useful for related changes that must participate in the same transaction.
Asynchronous PostOperation runs separately and does not share the originating transaction’s rollback boundary.
The distinction is essential when designing audit records, notifications, and external integrations.
3. Synchronous Versus Asynchronous Execution
| Dimension | Synchronous | Asynchronous |
|---|---|---|
| User waits for completion | Yes | No, for background execution |
| Participates in original transaction | Depending on stage and context | No |
| Can reject originating operation | Yes, within applicable transaction | No, after original commit |
| Failure handling | Error can propagate to caller | Background failure diagnostics |
| Suitable for | Validation, normalization, atomic related changes | Noncritical follow-up processing |
Asynchronous does not mean guaranteed immediate execution.
It means processing is delegated to the platform’s asynchronous infrastructure.
Engineers must account for execution delay, retries, duplicate side-effect risks, and eventual consistency where applicable.
4. Application Architecture
APP-011 Plug-in Pipeline Laboratory│├── C# Plug-in Assembly│ ├── PreValidation Probe│ ├── PreOperation Normalizer│ ├── PostOperation Audit│ ├── Asynchronous Observer│ └── Failure Injection│├── Dataverse│ ├── pea_pipelinerecord│ ├── pea_pipelineevent│ └── pea_pipelinescenario│├── Diagnostic Infrastructure│ ├── ITracingService│ ├── Plugin Trace Log│ ├── CorrelationId│ ├── OperationId│ └── ParentContext│└── Model-Driven App ├── Entry Dashboard ├── Pipeline Records ├── Pipeline Events ├── Scenario Configuration └── Execution Diagnostics
The plug-in is the primary technology under investigation.
The Model-Driven App is the operational interface for triggering record changes and inspecting their results.
5. Dataverse Data Model
5.1 Pipeline Record
Logical name: pea_pipelinerecord
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Experiment record |
pea_externalcode | Single line of text | Correlation identifier |
pea_amount | Decimal number | Update payload |
pea_normalizedname | Single line of text | PreOperation output |
pea_injectfailure | Yes/No | Failure injection flag |
pea_description | Multiple lines of text | Scenario details |
5.2 Pipeline Event
Logical name: pea_pipelineevent
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Event identifier |
pea_record | Lookup | Related Pipeline Record |
pea_stage | Whole number | Execution stage |
pea_mode | Whole number | Synchronous or asynchronous |
pea_depth | Whole number | Execution depth |
pea_isintransaction | Yes/No | Transaction context |
pea_message | Single line of text | Pipeline message |
pea_correlationid | Single line of text | Request correlation |
pea_operationid | Single line of text | Operation identifier |
pea_targetattributes | Multiple lines of text | Target attribute names |
pea_preimageamount | Decimal number | Previous amount |
pea_postimageamount | Decimal number | Resulting amount |
pea_executedon | Date and time | Execution timestamp |
5.3 Pipeline Scenario
Logical name: pea_pipelinescenario
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Scenario identifier |
pea_description | Multiple lines of text | Expected behavior |
pea_expectedstages | Single line of text | Expected stage sequence |
pea_expectedrollback | Yes/No | Expected transaction result |
pea_enabled | Yes/No | Scenario availability |
A critical design constraint is that diagnostic records created inside a transaction can be rolled back with the transaction.
Therefore, the experiment uses both Dataverse event records and ITracingService.
The Plugin Trace Log is particularly valuable when an intentional exception causes the transactional event records to disappear.
6. Understanding IPluginExecutionContext
The execution context exposes information about the operation that triggered the plug-in.
Important properties include:
| Property | Purpose |
|---|---|
MessageName | Dataverse message |
PrimaryEntityName | Target table |
PrimaryEntityId | Primary record identifier when available |
Stage | Pipeline stage |
Mode | Synchronous or asynchronous |
Depth | Execution depth |
IsInTransaction | Whether execution is within a transaction |
UserId | Effective execution user |
InitiatingUserId | Original initiating user |
CorrelationId | Correlation across related execution |
OperationId | Operation identifier |
InputParameters | Incoming message parameters |
OutputParameters | Result parameters |
PreEntityImages | Registered pre-operation snapshots |
PostEntityImages | Registered post-operation snapshots |
SharedVariables | Data shared across supported pipeline contexts |
ParentContext | Parent execution context when present |
These properties should be inspected, not assumed.
For example, Depth indicates nested execution depth but does not by itself identify an infinite loop.
Similarly, ParentContext should not be treated as a permanent contract for arbitrary business decisions.
7. Target Versus Entity Images
One of the most common plug-in mistakes is assuming that the Update Target contains the entire record.
For an Update message, Target normally contains the attributes supplied by the caller.
If the caller updates only pea_amount, the target may contain only that attribute.
if (target.Contains("pea_amount")){ decimal amount = target.GetAttributeValue<decimal>( "pea_amount");}
A missing attribute in Target does not necessarily mean the stored column is null.
It may simply mean the caller did not include it in the update request.
7.1 PreEntityImages
A Pre Image contains configured attribute values from before the core operation.
It can be used to compare previous and proposed values.
7.2 PostEntityImages
A Post Image contains configured attribute values from after the core operation.
It is useful for observing resulting values in supported pipeline stages.
7.3 Image registration
Images must be registered with the plug-in step.
The plug-in cannot assume that an image exists merely because its code references a particular alias.
For APP-011, use the aliases:
PreImagePostImage
Configure the required columns explicitly.
Avoid selecting every column without a reason.
Reference: Register plug-in steps.
8. Filtering Attributes
An Update plug-in can be registered with filtering attributes.
For example:
pea_namepea_amountpea_injectfailure
The step runs when the Update request includes at least one configured filtering attribute.
This is an important distinction:
Filtering attributes evaluate attributes included in the request, not necessarily whether their values actually changed.
If a client submits the same value already stored in Dataverse, the plug-in can still execute.
A robust plug-in should compare old and new values when actual change detection is required.
Filtering attributes can reduce unnecessary execution, but they do not replace business-level comparison logic.
9. Transaction Semantics and Rollback
Consider a synchronous PostOperation plug-in that creates a pea_pipelineevent record and then throws an exception.
Update Pipeline Record │ ▼PostOperation Plug-in │ ├── Create Pipeline Event │ └── Throw Exception │ ▼ Transaction Rollback
The originating update and the event record can both be rolled back.
This behavior is central to APP-011.
The experiment must distinguish between:
- A trace indicating that code executed.
- A record that was successfully committed.
- A record that was created temporarily but rolled back.
These are different kinds of evidence.
10. Plug-in Registration Matrix
The initial laboratory will register the following steps.
| Step | Message | Stage | Mode | Purpose |
|---|---|---|---|---|
| Pipeline Probe | Update | PreValidation | Synchronous | Inspect early context |
| Pipeline Normalizer | Update | PreOperation | Synchronous | Modify Target |
| Pipeline Audit | Update | PostOperation | Synchronous | Persist transactional event |
| Pipeline Observer | Update | PostOperation | Asynchronous | Observe committed operation |
The first implementation can use one reusable plug-in class registered multiple times, with stage-specific behavior.
For production code, separate classes may be preferable when responsibilities become distinct.
11. C# Plug-in Project
The proposed project structure is:
PowerPlatformEngineeringAcademy/└── Dataverse/ └── APP011.PluginPipelineLab/ ├── APP011.PluginPipelineLab.csproj ├── PipelineProbePlugin.cs └── README.md
The project should target the framework and package versions supported by the intended Dataverse plug-in deployment model.
For a conventional Dataverse plug-in assembly, this commonly means a .NET Framework 4.6.2-compatible build using the Dataverse SDK assemblies.
The exact deployment configuration must be checked against current Microsoft documentation before packaging.
Install the required SDK package:
dotnet add package Microsoft.CrmSdk.CoreAssemblies
The initial implementation will use IPlugin, IPluginExecutionContext, IOrganizationServiceFactory, and ITracingService.
It will avoid external dependencies unless they are required by the experiment.
12. Complete C# Plug-in Implementation
The following class is registered on multiple Update steps.
It performs three principal functions:
- Captures execution context through tracing.
- Normalizes the record name during PreOperation.
- Persists a diagnostic event during PostOperation.
It also supports intentional failure injection during synchronous PostOperation.
The implementation is designed for the standard Dataverse plug-in execution environment and avoids external dependencies.
PipelineProbePlugin.cs
using System;using System.Linq;using Microsoft.Xrm.Sdk;namespace PowerPlatformAcademy.Dataverse.Plugins{ public sealed class PipelineProbePlugin : IPlugin { private const string TableName = "pea_pipelinerecord"; private const string EventTable = "pea_pipelineevent"; public void Execute(IServiceProvider serviceProvider) { var context = (IPluginExecutionContext) serviceProvider.GetService( typeof(IPluginExecutionContext)); var tracing = (ITracingService) serviceProvider.GetService( typeof(ITracingService)); var factory = (IOrganizationServiceFactory) serviceProvider.GetService( typeof(IOrganizationServiceFactory)); if (context == null || tracing == null || factory == null) throw new InvalidPluginExecutionException( "Required plug-in services are unavailable."); if (context.MessageName != "Update" || context.PrimaryEntityName != TableName) return; if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity target)) return; tracing.Trace( "APP011 | Stage={0}; Mode={1}; Depth={2}; " + "Transaction={3}; User={4}; Initiator={5}; " + "Correlation={6}; Operation={7}", context.Stage, context.Mode, context.Depth, context.IsInTransaction, context.UserId, context.InitiatingUserId, context.CorrelationId, context.OperationId); tracing.Trace( "Target attributes: {0}", string.Join(",", target.Attributes.Keys.OrderBy(k => k))); Entity preImage = null; Entity postImage = null; if (context.PreEntityImages.Contains("PreImage")) preImage = context.PreEntityImages["PreImage"]; if (context.PostEntityImages.Contains("PostImage")) postImage = context.PostEntityImages["PostImage"]; if (context.Stage == 20) { NormalizeTarget(target, tracing); } // Persist diagnostics in synchronous PostOperation. // The record participates in the active transaction. if (context.Stage == 40 && context.Mode == 0) { var service = factory.CreateOrganizationService( context.UserId); WritePipelineEvent( service, context, target, preImage, postImage); bool injectFailure = target.Contains("pea_injectfailure") && target.GetAttributeValue<bool>( "pea_injectfailure"); if (injectFailure) { tracing.Trace( "APP011 intentional rollback requested."); throw new InvalidPluginExecutionException( "APP011 intentional PostOperation failure."); } } // Asynchronous PostOperation observer. if (context.Stage == 40 && context.Mode == 1) { var service = factory.CreateOrganizationService( context.UserId); WritePipelineEvent( service, context, target, preImage, postImage); } } private static void NormalizeTarget( Entity target, ITracingService tracing) { if (!target.Contains("pea_name")) return; string name = target.GetAttributeValue<string>("pea_name"); if (string.IsNullOrWhiteSpace(name)) return; string normalized = name.Trim().ToUpperInvariant(); target["pea_normalizedname"] = normalized; tracing.Trace( "Normalized name: {0}", normalized); } private static void WritePipelineEvent( IOrganizationService service, IPluginExecutionContext context, Entity target, Entity preImage, Entity postImage) { var diagnostic = new Entity(EventTable) { ["pea_name"] = $"APP011-{Guid.NewGuid():N}", ["pea_record"] = new EntityReference( TableName, context.PrimaryEntityId), ["pea_stage"] = context.Stage, ["pea_mode"] = context.Mode, ["pea_depth"] = context.Depth, ["pea_isintransaction"] = context.IsInTransaction, ["pea_message"] = context.MessageName, ["pea_correlationid"] = context.CorrelationId.ToString(), ["pea_operationid"] = context.OperationId.ToString(), ["pea_targetattributes"] = string.Join(",", target.Attributes.Keys.OrderBy(k => k)), ["pea_executedon"] = DateTime.UtcNow }; if (preImage != null && preImage.Contains("pea_amount")) { diagnostic["pea_preimageamount"] = preImage.GetAttributeValue<decimal>( "pea_amount"); } if (postImage != null && postImage.Contains("pea_amount")) { diagnostic["pea_postimageamount"] = postImage.GetAttributeValue<decimal>( "pea_amount"); } service.Create(diagnostic); } }}
12.1 What this implementation demonstrates
The plug-in captures the execution stage, mode, depth, transaction flag, user identity, correlation identifier, and target attributes.
During PreOperation, it modifies Target directly rather than issuing an additional update.
During synchronous PostOperation, it creates a diagnostic record inside the transaction.
During asynchronous PostOperation, it creates a diagnostic record through a separate execution path.
The intentional exception provides a reproducible rollback experiment.
12.2 Important limitations
The plug-in assumes that the configured user has permission to create pea_pipelineevent records.
It also assumes that the registered images use the aliases PreImage and PostImage.
The failure flag is read from Target. If the flag is already true in the database but is not included in a later Update request, this implementation will not inject a failure.
That is deliberate: the experiment investigates the contents of the Update request, not merely the current stored value.
13. Registration Procedure
After compiling the assembly, register it in the development environment.
Use the supported Plug-in Registration Tool or Power Platform Tools workflow.
Configure the following steps:
| Step | Message | Stage | Mode | Filtering attributes |
|---|---|---|---|---|
| PreValidation Probe | Update | PreValidation | Synchronous | pea_name, pea_amount, pea_injectfailure |
| PreOperation Normalizer | Update | PreOperation | Synchronous | pea_name, pea_amount, pea_injectfailure |
| PostOperation Audit | Update | PostOperation | Synchronous | pea_name, pea_amount, pea_injectfailure |
| Async Observer | Update | PostOperation | Asynchronous | pea_name, pea_amount, pea_injectfailure |
For the PostOperation steps, register appropriate Pre and Post Images.
Recommended image columns:
pea_namepea_amountpea_injectfailurepea_normalizedname
Use the aliases:
PreImagePostImage
The registration configuration is part of the experiment and must be documented alongside the C# source.
Microsoft confirms that image availability depends on the message and stage. For example, Post Images are available for appropriate PostOperation registrations, not PreOperation. <Cite ref=”turn158504search15″/>
14. Experiment A: PreOperation Normalization
Create a Pipeline Record through the Model-Driven App.
Update its name to:
pipeline laboratory
The PreOperation plug-in should write:
PIPELINE LABORATORY
into pea_normalizedname.
The expected outcome is that the original Update operation persists both the requested name and the normalized value.
No separate service.Update call is required for the normalization.
Verify the resulting values through the form and a fresh SDK or Web API retrieval.
15. Experiment B: Entity Images
Create a record with:
pea_amount = 1000
Then update it to:
pea_amount = 1500
Expected diagnostic values:
| Source | Expected amount |
|---|---|
| PreImage | 1000 |
| Target | 1500 |
| PostImage | 1500 |
This experiment establishes the distinction between the incoming request and the registered snapshots.
The results depend on correct image registration.
A missing image should be treated as a registration or configuration problem, not silently interpreted as a null business value.
16. Experiment C: Transaction Rollback
Update a record with:
pea_injectfailure = true
The synchronous PostOperation plug-in creates a diagnostic record and then throws InvalidPluginExecutionException.
Expected behavior:
- The Update operation fails.
- The original record retains its previous persisted values.
- The synchronous diagnostic record is rolled back.
- The asynchronous step for the failed originating operation does not produce a successful follow-up event.
- Tracing provides evidence of the attempted execution and exception, subject to trace configuration.
This experiment directly extends APP-008’s transaction analysis.
17. Experiment D: Asynchronous Processing
Perform a successful update with failure injection disabled.
The synchronous PostOperation step should execute before the original request completes.
The asynchronous PostOperation step should execute separately after the originating transaction commits.
The laboratory should measure the delay between the originating operation and the asynchronous event record.
Do not assume that the asynchronous event will appear immediately.
The experiment should use a bounded observation window and report whether processing completed within that window.
18. Experiment E: Recursion and Depth
A plug-in can trigger additional Dataverse operations.
Those operations can themselves enter the event pipeline.
For example:
Update Record A │ ▼Plug-in A │ ▼Update Record B │ ▼Plug-in B │ ▼Additional operation
This is legitimate when the architecture requires it.
However, updating the same record repeatedly can create recursive execution.
The Depth property is useful for diagnostics, but a blanket rule such as:
if (context.Depth > 1) return;
can incorrectly suppress legitimate nested operations.
A better design uses explicit conditions, carefully chosen filtering attributes, and idempotent business logic.
The laboratory should introduce a controlled recursive scenario and inspect the resulting depth and correlation values.
19. Tracing and Diagnostics
ITracingService is a fundamental diagnostic mechanism for Dataverse plug-ins.
It allows developers to record execution details without depending on transactional business records.
Enable the appropriate Plugin Trace Log setting in the development environment.
Use tracing to capture:
- Stage and mode.
- Depth.
- Transaction context.
- Correlation identifier.
- Target attribute names.
- Registered image availability.
- Business validation decisions.
- Exception details.
Do not log secrets or sensitive business values unnecessarily.
Microsoft documents plug-in tracing and debugging through the Plugin Trace Log and Plug-in Profiler. <Cite ref=”turn158504search11″/>
20. Model-Driven App: PEA Plug-in Pipeline Laboratory
The operational application will be named:
PEA Plug-in Pipeline Laboratory
Entry dashboard
| Metric | Purpose |
|---|---|
| Total pipeline events | Persisted diagnostic events |
| Synchronous events | Events from synchronous steps |
| Asynchronous events | Events from asynchronous steps |
| Transactional events | Events recorded with IsInTransaction = true |
| Latest execution | Most recent diagnostic event |
| Maximum observed depth | Highest recorded execution depth |
The dashboard must also include a view of recent Pipeline Records and recent Pipeline Events.
Pipeline Records
The form exposes:
- Name.
- Amount.
- Normalized name.
- Failure injection flag.
- Related pipeline events.
Pipeline Events
The view exposes:
- Stage.
- Mode.
- Depth.
- Transaction flag.
- Correlation identifier.
- Pre Image amount.
- Post Image amount.
Scenario Configuration
The scenario form documents expected stage behavior and rollback conditions.
Execution Diagnostics
A custom page may provide a stage-by-stage execution timeline grouped by correlation identifier.
The page must distinguish persisted diagnostic events from trace records that may survive failed transactions.
21. Performance and Architectural Considerations
Synchronous plug-ins add work to the originating Dataverse operation.
Expensive synchronous processing can increase user-visible latency and transaction duration.
A useful benchmark should measure:
- Baseline Update duration without plug-ins.
- Update duration with PreOperation normalization.
- Update duration with synchronous PostOperation logging.
- Time until asynchronous processing completes.
- Number of service requests generated.
- Number of plug-in executions.
- Failure and rollback rates.
The experiment should also compare a PreOperation modification of Target with an unnecessary secondary Update request.
The objective is to understand the additional execution cost and avoid redundant service calls.
22. Engineering Failure Matrix
| Failure | Investigation |
|---|---|
| Plug-in never executes | Check message, table, stage, mode, filtering attributes |
| Pre Image missing | Verify image registration and alias |
| Post Image missing | Verify stage and image configuration |
| Target column missing | Inspect actual Update payload |
| Unexpected recursion | Inspect depth, filtering attributes, and nested updates |
| Update rolled back | Inspect synchronous exception and transaction context |
| Async event delayed | Inspect asynchronous processing status |
| Diagnostic record missing | Determine whether transaction rollback removed it |
| Permission failure | Inspect execution identity and required privileges |
| Excessive latency | Measure synchronous work and additional service calls |
23. Microsoft Learn Technical References
- Event Framework — Execution stages, pipeline behavior, and transaction context.
- Write a plug-in — Plug-in implementation and service access.
- Register a plug-in — Step registration, filtering attributes, and images.
- Understand the execution context — Execution context properties and message parameters.
- Debug plug-ins — Tracing and Plug-in Profiler.
- Build and package plug-in code — Assembly packaging and runtime requirements.
- Scalable Customization Design: Database Transactions — Transaction boundaries and scalability.
Conclusion
The Dataverse plug-in execution pipeline is a server-side extension mechanism with explicit lifecycle, identity, and transaction semantics.
The execution stage determines when code runs.
The execution mode determines whether processing blocks the originating request or occurs asynchronously.
The execution context describes the operation, while Target and entity images expose different views of the record.
Transactions determine which changes survive failures.
The central engineering principle is:
A Dataverse plug-in cannot be understood from its C# source alone. Its registration, execution context, transaction boundary, and observed effects are equally important parts of its behavior.
APP-011 provides the foundation for deeper investigations into plug-in performance, Custom APIs, impersonation, advanced diagnostics, and enterprise server-side architecture.
Power Platform Engineering Academy — Five Years in Five Days
Article 011/100 — Plug-in Execution Pipeline Laboratory
Register deliberately. Inspect context. Trace execution. Verify persistence.
