Microsoft Dataverse — Complete Guide
Core Concepts · Data Modelling · Security · Business Logic · Plugins · Web API · Scenarios · Cheat Sheet
Top Hashtags: #MicrosoftDataverse, #PowerPlatform, #Dataverse, #PowerApps, #D365Developer, #LowCode, #MicrosoftDeveloper, #DataverseSecurity, #PowerPlatformALM, #BusinessApplications
Table of Contents
- Core Concepts — Basics
- Data Modelling & Relationships
- Security Model
- Business Logic & Extensibility
- Plugins Deep Dive
- Web API & Integration
- ALM & Solutions
- Scenario-Based Questions
- Cheat Sheet — Quick Reference
1. Core Concepts — Basics
What is Microsoft Dataverse and how does it relate to the Power Platform?
Dataverse is a cloud-based, low-code data platform built on Azure. It provides a secure, scalable, and structured storage layer for business data. It is the backbone of the Power Platform — Power Apps (Model-Driven), Power Automate, Power Pages, Copilot Studio, and Dynamics 365 all store and retrieve data from Dataverse.
Dataverse combines in a single managed service:
- Relational database (Azure SQL under the hood)
- Role-based security (table, record, and field level)
- Business logic (plugins, business rules, low-code plugins)
- Audit and compliance
- REST API (OData Web API)
- File and image storage
- Search (Dataverse Search / Azure Cognitive Search)
Key positioning: Dataverse is not just a database — it includes security, logic, audit, and API layers all built in.
What is the difference between Dataverse and Dataverse for Teams?
| Dataverse | Dataverse for Teams | |
|---|---|---|
| Hosting | Full Power Platform environment | Embedded in Microsoft Teams |
| Storage | Up to 4TB+ per environment | 2GB per team |
| Plugins | Supported | Not supported |
| Business Units | Full hierarchy | Not supported |
| Security Roles | Full custom roles | Owner/Member/Guest only |
| API access | Full Web API | Limited |
| Licence | Power Apps/D365 licence required | Included with Microsoft 365 |
| Best for | Enterprise applications | Simple team-level apps |
Warning: Dataverse for Teams cannot be upgraded to full Dataverse in-place — data must be migrated manually if you outgrow it.
What is a Dataverse environment and what types exist?
An environment is a container that holds Dataverse data, apps, flows, and other resources. All resources share the same Dataverse database within an environment.
| Environment Type | Description |
|---|---|
| Production | Live business data. Full backup/restore. Full SLA. |
| Sandbox | Development and testing. Can be reset/copied. Convertible to production. |
| Developer | Single-user personal environment. Free with Power Apps Developer Plan. |
| Default | One per tenant, auto-created. Every user can create apps — often ungoverned. |
| Trial | Temporary 30-day environment for testing premium features. |
What are the standard table types in Dataverse?
| Table Type | Description | Example |
|---|---|---|
| Standard | Custom tables created by makers | cr123_order, cr123_invoice |
| System | Built-in Dataverse tables, cannot be deleted | User, Team, Business Unit |
| Activity | Interaction tracking tables, inherit from Activity base | Email, Phone Call, Task, Appointment |
| Virtual | Surface external data without copying it | SQL table, SharePoint list |
| Elastic | High-volume, high-throughput, Cosmos DB-backed | IoT telemetry, event logs |
What column types are available in Dataverse?
Text types: Text, Multiline Text, Email, URL, Phone, Ticker Symbol
Number types: Whole Number, Decimal, Float, Currency
Date types: Date Only, Date and Time
Boolean: Yes/No (Two Options)
Choice: Choice (single select), Choices (multi-select)
Lookup: Lookup (to another table), Customer (Account or Contact), Owner
File/Image: File, Image
Special: Unique Identifier (GUID), Formula, Rollup, Calculated, Auto Number
Tip: Know the difference between Choice (option set — values stored in metadata) and Lookup (foreign key relationship to another table).
What is the primary key in Dataverse and can it be changed?
Every Dataverse table has a system-generated GUID as its primary key (e.g., accountid for Account, contactid for Contact). This is auto-generated on record creation and cannot be modified.
For integration scenarios where you need to identify records by an external system's ID, use Alternate Keys instead of relying on the GUID.
2. Data Modelling & Relationships
What relationship types exist in Dataverse and how do they differ?
| Relationship | Description | Implementation |
|---|---|---|
| One-to-Many (1:N) | One parent record → many child records | Lookup column on child table |
| Many-to-One (N:1) | Same as 1:N from child's perspective | Lookup column on child table |
| Many-to-Many (N:N) | Multiple records on both sides relate to each other | Auto-created intersection table |
Tip: Dataverse does not support N:N with attributes on the relationship. If you need data on the relationship itself (e.g., role or effective date), create a manual intersection table with two lookup columns.
What is the difference between a Lookup, Customer, and Owner column?
Lookup: references a single specific table. A Case's Account lookup can only point to Account records.
Customer: a polymorphic lookup that can reference either an Account OR a Contact. Used when the related party could be either type. Example: the customerid column on Case.
Owner: a system lookup that can reference either a User OR a Team. Controls who owns the record — used in security/record-level access.
Warning: You cannot create additional Customer columns — only one built-in
customeridpattern exists per table. For custom polymorphic lookups, use two separate lookup columns.
What are Calculated and Rollup columns and when do you use each?
Calculated column: computes its value from other columns on the same record using a formula. Recalculated every time the record is retrieved.
Example: FullName = FirstName + " " + LastName
Example: DaysOverdue = DIFFINDAYS(DueDate, NOW())
Example: DiscountedPrice = ListPrice * (1 - DiscountPercent / 100)
Rollup column: aggregates values from related child records. Calculated on a schedule (every hour or on-demand). Supports SUM, COUNT, MIN, MAX, AVG with optional filter conditions.
Example: Total Won Revenue = SUM(Opportunities.ActualRevenue)
where Status = "Won"
Example: Open Case Count = COUNT(Cases) where Status = "Active"
Example: Last Contact Date = MAX(Activities.ActualEnd)
Rule: Use Calculated for real-time single-record computations. Use Rollup for aggregating across child records — but it is not real-time (up to 1-hour delay).
What are alternate keys in Dataverse and why are they useful?
Alternate keys define one or more columns (or combinations) as a unique identifier for a record — an alternative to the system GUID primary key. Enforced at the database level as a unique constraint.
Key uses:
- Upsert via API — use the alternate key value in API calls to create-or-update without knowing the GUID
- External system ID matching — store the external system's ID, use it for idempotent syncs
- Prevent duplicates — database-level uniqueness enforcement
PATCH /api/data/v9.2/accounts(cr123_externalid='EXT-001')
Content-Type: application/json
{"name": "Contoso Ltd", "revenue": 500000}
This creates the account if EXT-001 doesn't exist, or updates it if it does — without querying for the GUID first.
What are Choice columns (Option Sets) and what are global vs local choices?
Local choice: option set defined for a single specific column on a single table. Values are unique to that column. Cannot be reused elsewhere.
Global choice: option set defined at the environment level and reused across multiple tables/columns. When values change, they update everywhere the global choice is used.
Best practice: Always use Global choices for values that represent the same concept across tables (e.g., a standard "Priority" choice used on Cases, Tasks, and Opportunities). Local choices are acceptable for table-specific options unlikely to be reused.
What is the purpose of Publisher and Solution in Dataverse?
Publisher: defines the customisation prefix (e.g., cr123_) applied to all custom components. Ensures no naming conflicts with other publishers or Microsoft's own components.
Solution: a container for all customisations — tables, columns, flows, apps, security roles, etc. Used for ALM — export from dev, import to prod. Solutions reference components; a component can be in multiple solutions.
Warning: Never use the default publisher prefix
new_in production solutions. Always create a custom publisher with a meaningful prefix for your organisation.
What are Virtual Tables and when should you use them?
Virtual tables surface data from external sources (SQL Server, SharePoint, Azure, REST APIs) inside Dataverse without physically copying the data. They appear and behave like regular Dataverse tables — usable in Power Apps, Power Automate, and Model-Driven apps.
Use when:
- Data must remain in its authoritative source (regulatory/compliance requirement)
- Real-time external data access is needed in Power Apps
- You want to avoid data duplication and sync complexity
- The external system already has an API or OData endpoint
Limitations: Virtual tables do not support plugins, business rules, or field-level security. They are read-only in most implementations unless the provider supports write operations.
3. Security Model
Explain the Dataverse security model — layers and how they interact.
Dataverse security has four layers that all must be satisfied:
| Layer | Controls |
|---|---|
| Environment access | User must have a licence and security role in the environment |
| Security roles | What the user can do (CRUD + Append/AppendTo/Assign/Share) on each table at a defined scope |
| Record-level access | Which specific records can be accessed — controlled by ownership and sharing |
| Field-level security | Individual column access — overrides table-level for sensitive fields |
Tip: All four layers must be satisfied for a user to access a specific field on a specific record. A user with table-level Read can still be blocked by field security or record ownership scope.
What are the access level scopes in a security role?
| Scope | Name | Records Accessible |
|---|---|---|
| None | — | No records |
| User | Basic | Only records the user personally owns |
| Business Unit | Local | Records owned by anyone in user's BU |
| Parent/Child BU | Deep | User's BU + all child BUs |
| Organisation | Global | All records in the entire environment |
Warning: Organisation (Global) scope is the most permissive. Never assign it carelessly. Most end-user roles should use User or Business Unit scope.
What are the Dataverse privileges beyond CRUD?
Create → create new records
Read → read record data
Write → update existing records
Delete → delete records
Append → attach THIS record to another record
e.g., adding a Note to a Case — the Note needs Append on Note table
AppendTo → allow another record to be attached TO this record
e.g., the Case needs AppendTo on Case table for Notes to attach
Assign → change the owner of a record
Share → share a record with another user/team beyond role access
Tip: Append + AppendTo is the most commonly misunderstood privilege pair. When a relationship write fails despite having Create and Write, check Append/AppendTo — it is almost always the missing piece.
What is the difference between Record Sharing and Security Roles?
Security roles: policy-based, apply to all records matching the scope. Broad, consistent, manageable at scale.
Record sharing: grants access to a specific individual record to a specific user or team — beyond what their security role allows. Used for exceptions.
Warning: Over-reliance on record sharing creates an unmanageable access model at scale. Design security roles to cover 95%+ of access requirements — use sharing only for genuine exceptions.
What are Business Units and how do they affect data access?
Business Units (BUs) form a hierarchical organisational structure. Every user and record belongs to exactly one BU. BUs affect which records a user can access based on their security role scope.
Common BU structures:
- Geography: North / South / East / West regions
- Department: Sales / HR / Finance / Operations
- Subsidiary: Company A / Company B / Company C
All records created by a user in a BU are owned by that BU by default. Security role scope determines how far up/down the hierarchy a user can see.
What is Field Security in Dataverse and how is it configured?
Field Security restricts read/write/create access on individual columns, configured via Field Security Profiles.
Configuration steps:
- Enable field security on the column (in column definition — toggle on)
- Create a Field Security Profile defining allowed/denied access levels for that column
- Assign the Field Security Profile to users or teams
Users without the profile see the field as blank and cannot write to it — even if they have table-level Read/Write. The System Administrator role bypasses field security.
Tip: Field security is additive to table security — a user needs both table-level Read AND the appropriate Field Security Profile to see a protected column.
What are Teams in Dataverse and what types exist?
| Team Type | Description |
|---|---|
| Owner team | Can own records. Security roles assigned to the team apply to all members. |
| Access team | Cannot own records. Used for per-record sharing — members get access to specific records via team templates. |
| AAD Group team | Synced with an Azure AD security group or Microsoft 365 group. Membership managed in Azure AD. |
4. Business Logic & Extensibility
What are the server-side extensibility options in Dataverse?
| Mechanism | Language | Runs In Transaction | Best For |
|---|---|---|---|
| Plugin (sync) | C# .NET | Yes | Validation, field auto-population, cross-record updates |
| Plugin (async) | C# .NET | No | Notifications, external calls, logging |
| Low-code plugin | Power Fx | Yes | Maker-authored server-side logic |
| Custom API | C# .NET | Configurable | Reusable business operations as API endpoints |
| Cloud flow | Power Automate | No | Process automation, non-transactional logic |
| Business rule | Declarative | Entity scope: Yes | Field show/hide/set/validate |
| Calculated column | Formula | On retrieve | Derived field values |
| Rollup column | Aggregation | Hourly schedule | Aggregating child record values |
What are Business Rules in Dataverse and what can they do?
Business Rules are declarative no-code rules configured in the form designer. They can execute client-side (form only) or server-side (all saves).
Capabilities:
- Show or hide fields
- Enable or disable (lock) fields
- Set field values
- Set field requirement level (required / not required / recommended)
- Show validation error messages
Scope:
- Form scope: runs in the browser form only — does not fire on API or flow saves
- Entity (table) scope: runs on ALL save operations — form, API, Power Automate, plugin — server-side enforcement
Critical: Entity scope business rules run server-side on every save including via API and flows. Form scope only runs in the browser. This distinction separates mid-level from senior candidates.
What are Dataverse low-code plugins (Power Fx plugins)?
Low-code plugins allow makers to write server-side Dataverse logic using Power Fx — without writing C#. They run synchronously inside the database transaction.
Example — auto-set priority on create:
If(Revenue > 100000, Set(Priority, 'High'), Set(Priority, 'Normal'))
Example — validate on save:
If(EndDate < StartDate,
Error("End date cannot be before start date"))
Example — auto-populate calculated field:
Patch(ThisRecord, {FullName: FirstName & " " & LastName})
Tip: Low-code plugins bridge the gap between Business Rules (limited logic) and traditional C# plugins (requires developer). Ideal for makers who need server-side logic.
What is Dataverse auditing and what can be tracked?
Dataverse auditing tracks create, update, delete, and access operations on records and fields.
Configuration levels:
- Environment level: enable auditing globally
- Table level: enable for specific tables
- Column level: enable for specific columns
What is captured:
- Who made the change (user)
- When the change was made
- Old value and new value for each changed column
- Operation type (Create / Update / Delete / Access)
Audit logs are stored in the Audit table in Dataverse and accessible via the Audit History subgrid on records or via the Web API.
5. Plugins Deep Dive
What are Plugins in Dataverse and how do they work?
Plugins are C# .NET class libraries implementing the IPlugin interface that execute when a Dataverse event (message) occurs on a table. Registered on a specific step using the Plugin Registration Tool.
public class AutoSetPriority : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
var serviceFactory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
var tracingService = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity target)
{
if (target.Contains("revenue") &&
(decimal)target["revenue"] > 100000)
{
target["cr123_priority"] = new OptionSetValue(1); // High
}
tracingService.Trace("AutoSetPriority executed.");
}
}
}
What is the Plugin execution pipeline?
User/API call → Dataverse processes request
Stage 10 — PreValidation
→ OUTSIDE the database transaction
→ Can read current database state (pre-change)
→ Use for: input validation, permission checks, reading current values
→ Exception here: does not roll back DB (no transaction yet)
Stage 20 — PreOperation
→ INSIDE the database transaction
→ Target entity not yet written to DB
→ Use for: modifying input values before save, auto-populating fields
→ Can modify InputParameters["Target"] to change what gets saved
→ Exception here: rolls back the entire transaction
Stage 30 — Main Operation
→ Dataverse writes to the database
Stage 40 — PostOperation
→ INSIDE the database transaction
→ Record already written to DB
→ Use for: creating related records, chaining operations
→ context.OutputParameters["id"] has the new record's GUID
→ Exception here: rolls back the entire transaction including stage 30
Tip: PreOperation is the most commonly used stage — you can still modify the record before it saves. PostOperation is used when you need the new record's GUID.
What is the difference between synchronous and asynchronous plugins?
| Synchronous | Asynchronous | |
|---|---|---|
| Transaction | Inside DB transaction | Outside transaction |
| User experience | Blocks UI until complete | Non-blocking |
| Timeout | 2 minutes max | 2 minutes max per attempt |
| Rollback | Throws exception = full rollback | Cannot rollback DB |
| Best for | Validation, field calculation | Notifications, external calls, logging |
| Performance impact | Direct — slows the save operation | None on save operation |
Warning: Never do long-running operations (external HTTP calls, large queries) in synchronous plugins — they slow every save operation for every user.
What information is available in the Plugin execution context?
context.MessageName // "Create", "Update", "Delete"
context.Stage // 10, 20, 40
context.Mode // 0 = Sync, 1 = Async
context.PrimaryEntityName // "account", "contact"
context.PrimaryEntityId // GUID of record
context.InputParameters["Target"] // Entity being created/updated
context.InputParameters["id"] // GUID for Delete
context.OutputParameters["id"] // GUID of newly created record (PostOp)
context.PreEntityImages["preImage"] // Record values BEFORE the operation
context.PostEntityImages["postImage"]// Record values AFTER the operation
context.UserId // ID of user who triggered the operation
context.InitiatingUserId // Original user (different from UserId in async)
context.Depth // Call depth (prevent infinite plugin loops — check Depth == 1)
context.OrganizationId // Environment GUID
What are Pre and Post Entity Images and why are they important?
Entity images are snapshots of the record's data registered as part of the plugin step.
Pre-image: values of the record BEFORE the current operation. Available in Update and Delete steps. Must be explicitly registered in the Plugin Registration Tool.
Post-image: values of the record AFTER the current operation. Available in PostOperation of Create and Update. Contains all column values including those not in the Target.
// Why you need pre-images:
// On Update, context.InputParameters["Target"] only contains
// columns that CHANGED. To know ALL column values, register a pre-image.
if (context.PreEntityImages.Contains("preImage"))
{
var preImage = context.PreEntityImages["preImage"];
var oldStatus = preImage.GetAttributeValue<OptionSetValue>("statecode");
var newStatus = target.GetAttributeValue<OptionSetValue>("statecode");
if (oldStatus?.Value != newStatus?.Value)
{
// status changed — trigger notification
}
}
6. Web API & Integration
What is the Dataverse Web API and what does it support?
The Dataverse Web API is a RESTful OData v4 API providing full CRUD access, plus actions, functions, and batch operations. Uses OAuth 2.0 authentication.
Base URL: https://{org}.crm.dynamics.com/api/data/v9.2/
CRUD operations:
GET /accounts ← list all accounts
GET /accounts({guid}) ← single record
POST /accounts ← create (returns 204 + OData-EntityId header)
PATCH /accounts({guid}) ← update
DELETE /accounts({guid}) ← delete
OData query options:
?$select=name,revenue,createdon ← specific columns
?$filter=revenue gt 100000 and statecode eq 0 ← filter
?$orderby=createdon desc ← sort
?$top=25&$skip=50 ← pagination
?$expand=primarycontactid($select=fullname) ← join related table
?$count=true ← include total count
Upsert via alternate key:
PATCH /accounts(cr123_externalid='EXT-001')
Execute action (Custom API):
POST /cr123_CalculateDiscount
What is FetchXML and when would you use it over OData?
FetchXML is an XML-based query language native to Dataverse. Supports advanced querying not available in OData.
<fetch top="10" aggregate="true">
<entity name="opportunity">
<attribute name="estimatedvalue"
aggregate="sum" alias="TotalValue"/>
<attribute name="ownerid"
groupby="true" alias="Owner"/>
<filter>
<condition attribute="statecode" operator="eq" value="0"/>
</filter>
<link-entity name="account"
from="accountid" to="accountid" alias="acct">
<attribute name="industrycode" alias="Industry"/>
<filter>
<condition attribute="statecode" operator="eq" value="0"/>
</filter>
</link-entity>
</entity>
</fetch>
Use FetchXML when you need:
- Aggregate queries (SUM, COUNT, AVG, GROUP BY)
- Complex multi-table joins with filters on joined tables
- Queries on link-entity attributes
- Features not supported in OData
Pass FetchXML via Web API:
GET /accounts?fetchXml=<fetch...>
What is Change Tracking in Dataverse and how is it used for integration?
Change Tracking allows external systems to retrieve only records that have changed since the last sync.
Initial sync — get all records + deltaLink:
GET /accounts?$select=name,revenue
Response: @odata.deltaLink = ...?$deltatoken=XYZ123
Store the deltaToken. On next sync:
GET /accounts?$deltatoken=XYZ123
Returns: only new/modified records + deleted record IDs
Use in integration:
- Enable Change Tracking on the table
- Store
deltaTokenafter each sync run - Pass
deltaTokenon next sync to get only changes since last run - Process inserts, updates, and deletes separately
Tip: Change Tracking is far more efficient than querying by
ModifiedOndate — it is the recommended pattern for all scheduled data sync integrations.
What are Dataverse webhooks and how do they enable real-time integration?
Webhooks push event notifications to an external HTTPS endpoint when a Dataverse event occurs. Registered via the Plugin Registration Tool alongside plugins.
How it works:
- Register a webhook endpoint URL in the Plugin Registration Tool
- Register a step (table + message + stage) to trigger the webhook
- When the event fires, Dataverse POSTs the execution context payload to the URL
- The external system must respond within 60 seconds
Webhooks vs Service Bus:
- Webhooks: simple HTTPS endpoints — Azure Functions, Logic Apps, Power Automate HTTP trigger. Fire-and-forget with basic retry.
- Azure Service Bus: guaranteed delivery, configurable retry, decoupled async processing. Use for critical integrations where message loss is unacceptable.
What is the $batch endpoint and when should you use it?
The $batch endpoint allows sending multiple API operations in a single HTTP request — up to 1000 operations per batch.
POST /api/data/v9.2/$batch
Content-Type: multipart/mixed; boundary=batch_boundary
--batch_boundary
Content-Type: application/http
POST /api/data/v9.2/accounts HTTP/1.1
{"name": "Company A"}
--batch_boundary
Content-Type: application/http
POST /api/data/v9.2/accounts HTTP/1.1
{"name": "Company B"}
--batch_boundary--
Use $batch when:
- Importing large numbers of records (reduces API calls from N to N/1000)
- Performing atomic multi-record operations (use
changesetinside batch for transaction) - Integration scenarios requiring high throughput
7. ALM & Solutions
How do you manage Dataverse customisations with solutions?
# Export solution
pac solution export --path ./solution.zip --name MyDataverseSolution --managed
# Import solution
pac solution import --path ./solution.zip
# Clone solution for development
pac solution clone --name MyDataverseSolution --outputDirectory ./src
# Publish all customisations
pac solution publish
Solution component types:
- Tables (with columns, relationships, forms, views, charts)
- Global choices
- Security roles
- Business rules
- Plugins and Custom APIs
- Power Apps (Canvas and Model-Driven)
- Power Automate flows
- Connection references and environment variables
What is the difference between managed and unmanaged solutions?
| Managed | Unmanaged | |
|---|---|---|
| Editable in target | No — read-only components | Yes — full editing |
| Used in | Production, UAT | Development |
| Delete behaviour | Removes all components on solution delete | Components remain |
| Layering | Stacks on top of other solutions | Single layer |
| Recommended for | Deployment pipeline output | Active development |
What are solution layers and why do they matter?
Dataverse supports layered customisations — multiple solutions can customise the same component, and layers are applied in a defined order (base → extensions). The top layer wins.
Why it matters:
- Microsoft updates apply as a base layer — your customisations overlay them
- ISV solutions can be extended without modifying the original
- Unmanaged customisations always sit on top — they can accidentally override managed solution behaviour
- Use
pac solution checkto detect layer conflicts before deployment
8. Scenario-Based Questions
Scenario: Design a Dataverse data model for a multi-tenant B2B application.
Requirement: Multiple companies use the system. Each company's data must be completely isolated.
Architecture:
- Business Units as tenant boundaries — create one BU per tenant company
- Assign users to their company's BU — never to the root BU
- Security roles with BU scope — users can only read/write records in their own BU
- All custom tables use organisation-owned records with BU-partitioned ownership
- Alternate keys on each table for the tenant's external system ID — enables upsert
- Service accounts for integrations use Organisation scope — strictly controlled
- Virtual tables if tenant data must remain in their own systems
Tip: Business Unit per tenant is the standard Dataverse multi-tenancy pattern. Never rely on app-level filtering — enforce isolation at the security layer.
Scenario: A plugin is causing records to take 8 seconds to save. How do you diagnose and fix it?
Situation: Saving an Account record takes 8+ seconds. A synchronous PostOperation plugin is registered on Account Update.
- Plugin Profiler in Plugin Registration Tool — capture execution timing, identify slow step
- Check for outbound HTTP calls inside the sync plugin — move to async or webhook pattern
- Check for
RetrieveMultiplewithout proper filters — full table scan on every save is a common culprit - Move non-critical logic (notifications, external calls, logging) to an asynchronous plugin
- Replace outbound HTTP with webhook → Azure Function pattern — keeps plugin thin
- Consider replacing entirely with a Power Automate cloud flow for non-transactional logic
Scenario: Design a reliable nightly data sync from external SQL into Dataverse.
Design:
- Alternate keys on Dataverse tables using the SQL primary key — enables upsert
- Change Tracking on SQL (SQL Server CT or
ModifiedOncolumn) — extract only changed rows - Azure Data Factory or Power Automate — schedule nightly pipeline
- Upsert via alternate key —
PATCHto Web API using external ID $batchendpoint — send up to 1000 upserts per HTTP request- Error handling — capture failed records, retry with exponential backoff, log to Dataverse audit table
- Idempotency — re-running the sync for the same dataset produces the same result
Scenario: A user can't see a field on a Model-Driven form despite having Read on the table.
Three causes — check in order:
-
Field Security Profile — the column has field-level security enabled. User's account or team is not assigned the profile granting Read. Fix: assign the Field Security Profile to the user or their team.
-
Business Rule — a Business Rule is hiding the field based on another column's value. Check all Business Rules with "Form" scope on the table — one may have a hide condition matching the user's data.
-
Wrong form — the field is not on the form shown to this user's security role. Check form order and role assignment — the user may be seeing a different Main form that doesn't include the field.
Critical: Field Security Profile absence is the most common cause. Always check it first when a field shows blank despite table-level Read access.
Scenario: How do you prevent a plugin from causing an infinite loop?
Problem: A PostOperation plugin on Account Update calls service.Update() to set a flag — which re-triggers the same plugin, causing infinite recursion.
Fix — check execution depth:
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
// Prevent infinite loop — only execute on direct user calls (depth 1)
if (context.Depth > 1) return;
// Rest of plugin logic...
}
Alternative fix: Use a specific column update in the UpdatedAttributes filter on the plugin step — so the plugin only fires when specific columns change, not when your flag column changes.
9. Cheat Sheet — Quick Reference
Dataverse Web API Quick Reference
Authentication: OAuth 2.0 Bearer token
Base URL: https://{org}.crm.dynamics.com/api/data/v9.2/
GET /tablename → list records
GET /tablename({guid}) → single record
POST /tablename → create (no body ID)
PATCH /tablename({guid}) → update (partial)
DELETE /tablename({guid}) → delete
PATCH /tablename(altkey='value') → upsert via alternate key
OData params:
$select=col1,col2 only these columns
$filter=col eq 'value' filter conditions
$orderby=col desc sort
$top=N&$skip=M page
$expand=lookup($select=x) join related
$count=true include total count
$apply=aggregate(...) aggregate
Security Privileges Reference
Create, Read, Write, Delete → standard CRUD
Append → this record attaches to another
AppendTo → another record can attach to this one
Assign → change record owner
Share → grant access beyond role
Scope levels (least → most permissive):
None → User → Business Unit → Deep → Organisation
Plugin Key Reference
Stages:
10 = PreValidation (outside transaction, can read DB)
20 = PreOperation (inside transaction, modify Target)
30 = Main operation (DB write)
40 = PostOperation (inside transaction, record saved)
Modes:
0 = Synchronous (blocks operation, in transaction)
1 = Asynchronous (non-blocking, outside transaction)
Context properties:
context.MessageName "Create" / "Update" / "Delete"
context.PrimaryEntityName table logical name
context.PrimaryEntityId record GUID
context.InputParameters["Target"] entity being operated on
context.PreEntityImages["pre"] values before operation
context.PostEntityImages["post"] values after operation
context.Depth loop guard (check == 1)
context.UserId user triggering the operation
Prevent infinite loop:
if (context.Depth > 1) return;
Common Column Types Quick Reference
Single line of text → Text (max 4000 chars)
Multi-line text → Multiline Text (max 1M chars)
Whole number → Int (no decimals)
Decimal number → Decimal (precision configurable)
Currency → Currency (links to currency table)
Date only → Date Only
Date and time → DateTime (UTC stored, timezone displayed)
Yes/No → Boolean
Single select list → Choice (local or global)
Multi select list → Choices
Reference to 1 table → Lookup
Reference to Account/Contact → Customer (polymorphic)
Owner (User or Team) → Owner
Formula → Calculated (real-time)
Aggregation → Rollup (hourly)
Top 10 Tips
- Dataverse is not just a database — lead with: security, logic, API, audit all built in. Sets the right framing immediately.
- Append + AppendTo is the most misunderstood privilege. Know the Note-to-Case example cold — it comes up in every security question.
- Entity scope vs Form scope Business Rules — entity scope runs server-side on ALL saves including API/flow. Form scope = browser only.
- PreOperation stage is where you modify the record before save (
InputParameters["Target"]). PostOperation is where you use the new record's GUID. - Alternate keys for upsert — this is the answer to "how do you sync data from an external system without managing GUIDs." A must-know for integration questions.
- Change Tracking over ModifiedOn queries — Change Tracking with deltaToken is the correct integration pattern. Saying "I filter by ModifiedOn" is a junior answer.
context.Depth > 1check to prevent infinite plugin loops — ask about this almost universally in plugin questions.- Plugin in transaction = rollback on exception — synchronous Pre/PostOperation plugins that throw
InvalidPluginExecutionExceptionroll back the entire operation including the DB write. - Field Security Profile absence is the most common cause of "field shows blank despite Read access." Mention it first in any field visibility question.
- Virtual tables vs data sync — know when to recommend Virtual Tables (data stays in source, real-time) vs Change Tracking sync (data copied, offline-capable). This is an architect-level question.
No comments:
Post a Comment