A Dataverse plug-in starts at Execute. Cast the services once, then read the context and call the organization service from there.
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 ));
var service = factory. CreateOrganizationService (context.UserId);
Everything about the current request is on context.
Field Code What it tells you MessageName context.MessageNameSDK message being processed (Create, Update, Delete, Assign, and so on) Stage context.StagePipeline position: 10 PreValidation (outside the transaction), 20 PreOperation, 40 PostOperation (30 is platform-only) Mode context.Mode0 synchronous (caller waits, shares the transaction) or 1 asynchronous (queued system job) Depth context.DepthTimes the pipeline entered this request. Greater than 1 means it re-entered; use it to stop a loop CorrelationId context.CorrelationIdShared GUID for every plugin and system job in one user operation OperationId context.OperationIdGUID of the related system job (populated for asynchronous execution) OperationCreatedOn context.OperationCreatedOnWhen that related system job was created IsExecutingOffline context.IsExecutingOfflineTrue when the plugin runs in Dynamics 365 for Outlook while the client is offline IsOfflinePlayback context.IsOfflinePlaybackTrue when Outlook is replaying an offline event during sync back to the server IsInTransaction context.IsInTransactionTrue inside the database transaction (PreOperation and synchronous PostOperation). False for PreValidation and async
Field Code What it tells you UserId context.UserIdUser the plugin is running as. This user’s security roles are enforced InitiatingUserId context.InitiatingUserIdUser who actually started the operation, even if a later step impersonates someone else BusinessUnitId context.BusinessUnitIdBusiness unit of the user in UserId OwningExtension context.OwningExtensionEntityReference of the registered SDK message processing step that is executing
Field Code What it tells you PrimaryEntityName context.PrimaryEntityNameLogical name of the table the message targets (account, contact, and so on) PrimaryEntityId context.PrimaryEntityIdGUID of that record. Often empty on Create before PostOperation; read OutputParameters["id"] there SecondaryEntityName context.SecondaryEntityNameSecond table for Associate and Disassociate. Empty for most messages OrganizationName context.OrganizationNameUnique name of the Dataverse environment this plugin is running in
Read the message payload from context.InputParameters.
Key Code What it tells you Target context.InputParameters["Target"]Entity for Create and Update, or EntityReference for Delete EntityMoniker context.InputParameters["EntityMoniker"]Record pointer on SetState, when the message does not pass a full Entity Assignee context.InputParameters["Assignee"]User or team EntityReference on Assign Relationship context.InputParameters["Relationship"]Relationship schema name on Associate and Disassociate State / Status context.InputParameters["State"]State and status codes on SetState. Status is context.InputParameters["Status"] Other keys context.InputParameters["Query"]Keys that exist only for that message, such as ColumnSet and RelatedEntities
Read results from context.OutputParameters.
Key Code What it tells you id context.OutputParameters["id"]GUID of the new record on Create. Available in PostOperation BusinessEntity context.OutputParameters["BusinessEntity"]Record returned by Retrieve Custom results context.OutputParameters["YourResponseProperty"]Other outputs for that message, including custom API response properties
An in-memory bag for this request only. Parent and child contexts can read it. It is not stored on the row.
Use Code What it tells you Custom flag context.SharedVariables["skipPricing"] = trueA later step in the same request can branch without querying again Calculated value context.SharedVariables["total"] = amountA result computed early in the pipeline and reused by a later stage Any key context.SharedVariables["key"]Read a value a previous step stored
The dictionary key is the image name you registered on the step.
Image Code What it tells you Before the change context.PreEntityImages["PreImage"]Registered columns as they were before the change. Used on Update and Delete, not Create. Only the columns configured on the step are included
Image Code What it tells you After the change context.PostEntityImages["PostImage"]Record after the change, with Target merged into the database values. Only the columns configured on the step are included
Field Code What it tells you Calling pipeline context.ParentContextExecution context of the plugin that triggered this one. Null on the top-level user request. Walk context.ParentContext.MessageName to see the chain
tracing.Trace appends one line to the plug-in trace log. The line shows up when plug-in trace logging is Exception or All. The trace is kept when the transaction rolls back.
What to log Code A line tracing.Trace("message")Entry tracing.Trace($"{context.MessageName} {context.Stage}")An id or option tracing.Trace($"id={context.PrimaryEntityId}")Which branch ran tracing.Trace("branch: skip, depth > 1")A slow call tracing.Trace($"retrieve {sw.ElapsedMilliseconds} ms")Before you throw tracing.Trace(ex.ToString())
Leave secrets and full records out of the log.
CreateOrganizationService returns a service bound to a user. Pass a GUID to choose who the calls run as.
Run as Code What it tells you Step user factory.CreateOrganizationService(context.UserId)The user the step is already executing as. That user’s privileges apply Original caller factory.CreateOrganizationService(context.InitiatingUserId)Ignores later impersonation SYSTEM factory.CreateOrganizationService(null)Bypasses user privilege checks. Use this rarely Another user factory.CreateOrganizationService(otherUserId)Impersonates that user. Requires the act-on-behalf-of-another-user privilege
These are the calls on the service returned above.
Call Code What it does Create service.Create(entity)Insert a record and return its GUID Retrieve service.Retrieve(logicalName, id, columnSet)Read one record by id and a ColumnSet RetrieveMultiple service.RetrieveMultiple(query)Query with QueryExpression or FetchXML and return an EntityCollection Update service.Update(entity)Write only the attributes set on the Entity Delete service.Delete(logicalName, id)Remove a record by logical name and id Associate service.Associate(logicalName, id, relationship, relatedEntities)Link records through a relationship schema name (1:N or N:N) Disassociate service.Disassociate(logicalName, id, relationship, relatedEntities)Remove that link Execute service.Execute(request)Send any OrganizationRequest: typed request classes, messages with no dedicated method, and custom APIs
Request Code What it does AssignRequest service.Execute(new AssignRequest())Change the owner to a user or team SetStateRequest service.Execute(new SetStateRequest())Set statecode and statuscode (activate, deactivate, resolve) WinOpportunityRequest service.Execute(new WinOpportunityRequest())Close an opportunity as won CloseIncidentRequest service.Execute(new CloseIncidentRequest())Resolve a case AddMembersTeamRequest service.Execute(new AddMembersTeamRequest())Add users to an owner team GrantAccessRequest service.Execute(new GrantAccessRequest())Share a record by granting privileges to a user or team Custom API service.Execute(new OrganizationRequest("your_customapi"))Call a custom API or custom action by its unique name