Solving Dataverse Concurrency Issues Using Azure Service Bus Queue and Azure Functions



Concurrency issues are one of the most common challenges when building high-volume solutions in Microsoft Dataverse. The problem becomes even more visible when multiple transactions attempt to update the same record simultaneously.

Although Dataverse provides optimistic concurrency support, many real-world implementations still experience:

  • race conditions
  • overwritten values
  • inconsistent totals
  • API throttling
  • Service Protection limits

In this post, we will understand how Azure Service Bus Queue and Azure Functions can help solve these issues in a scalable and reliable way.


The Business Scenario

For this demo, two custom tables are used:

Contact

Stores:

  • Total Points

Order Point

Stores:

  • Transaction Date
  • Points
  • Balance Points
  • Contact

The requirement is:

Maintain accurate Contact.TotalPoints while also adjusting Order Point.BalancePoints based on point usage.

The rule is:

SUM(OrderPoint.Points)
=
SUM(OrderPoint.BalancePoints)
=
Contact.TotalPoints

Understanding the Concurrency Problem

Imagine the following transactions happening simultaneously for the same Contact:

TransactionPoints
Order A+20
Order B-50
Order C+10
Order D-15

Now suppose the current total points are:

100

What Happens Without Proper Concurrency Handling?

Two requests execute at nearly the same time.

Request A

Reads:

100

Calculates:

100 + 20 = 120

Request B

Reads:

100

Calculates:

100 - 50 = 50

Race Condition

If Request B updates last, the final value becomes:

50

instead of:

70

One update overwrites another.

This is called:

Lost Update Problem

Why Plugins Alone May Not Be Enough

Many developers try solving this using:

  • plugins
  • locking logic
  • optimistic concurrency
  • row versioning

While these approaches help, they may still struggle under heavy load because:

  • Dataverse processes many requests in parallel
  • API throttling occurs
  • transaction contention increases
  • Service Protection limits apply

At scale, synchronous processing becomes difficult to manage.


The Queue-Based Architecture

Instead of processing everything immediately inside Dataverse, the architecture becomes:

Dataverse

Plugin

Azure Service Bus Queue

Azure Function

Dataverse Update

This reduces contention significantly.


Why Azure Service Bus Queue?

Azure Service Bus provides:

  • asynchronous processing
  • retries
  • message ordering
  • buffering
  • scalability

Rather than having hundreds of plugins updating the same Contact simultaneously, requests are queued and processed safely.


The Most Important Concept: Sessions

The key to this solution is:

SessionId

Inside the plugin:

var message = new ServiceBusMessage(json)
{
SessionId = contactId.ToString()
};

Why SessionId Matters

All messages for the same Contact share the same SessionId.

Example:

Contact A
→ +20
→ -10
→ +5

All these messages belong to one session.

Azure Service Bus guarantees:

  • ordered processing
  • sequential execution within the same session

Important Advantage

Same Contact

Processed sequentially.


Different Contacts

Processed concurrently.

This means:

  • consistency is maintained
  • scalability is preserved

Plugin Responsibilities

The plugin itself becomes lightweight.

Its job is only to:

  1. Read transaction data
  2. Convert to JSON
  3. Push message to queue

Example:

var json = JsonConvert.SerializeObject(
new PointModel
{
Id = input.Id,
BalancePoints = input.tmy_Points.GetValueOrDefault(),
TransactionDate =
input.tmy_TransactionDateTime.GetValueOrDefault(),
IsUpdated = true
});

Then:

sender.SendMessageAsync(message)

Why This Is Better

Instead of heavy calculations inside plugins:

  • Dataverse transaction time becomes shorter
  • plugin execution becomes faster
  • database contention decreases

Azure Function Processing

The Azure Function processes queued messages.

Trigger:

[ServiceBusTrigger(
"orderpoints",
IsSessionsEnabled = true,
IsBatched = true)]

Important features:

  • Session enabled
  • Batch processing enabled

Processing Flow

The Azure Function performs:

Step 1 — Retrieve Contact

organizationService.Retrieve(...)

Gets current Total Points.


Step 2 — Retrieve Existing Active Points

GetActivePoints(...)

Loads all existing balance records.


Step 3 — Merge Existing + Incoming Transactions

var mergePoints =
activePoints.Concat(orderMessages)

This creates one ordered collection.


Example

Existing balances:

RecordBalance
P1+100
P2+50

Incoming transaction:

RecordBalance
P3-120

Step 4 — Knock-Off Calculation

The algorithm offsets negative balances against positive balances.

Result:

RecordFinal Balance
P10
P230
P30

This maintains consistency.


Why Ordering Is Critical

Without ordering:

-120
+50
+100

may process unpredictably.

Incorrect balances can occur.

Sessions guarantee proper sequence.


Bulk Updates Using ExecuteMultiple

Updates are grouped:

Chunk(150)

Then updated using:

ExecuteMultipleRequest

Benefits:

  • fewer API calls
  • reduced throttling
  • improved performance

Parallel Optimization

The implementation also uses:

Parallel.ForEach(...)

This improves throughput while preserving session ordering.


Why This Architecture Scales Better

Traditional plugin-heavy approach:

Immediate update
→ locking
→ contention
→ throttling

Queue-Based Architecture

Queue messages
→ process asynchronously
→ order safely
→ batch updates

This dramatically reduces concurrency problems.


Real-World Use Cases

This architecture works well for:

  • loyalty point systems
  • wallet balances
  • inventory calculations
  • financial adjustments
  • reward systems
  • consumption tracking

Any scenario where:

  • multiple transactions affect the same parent record
  • ordering matters
  • consistency is critical

Key Takeaway

The real solution to concurrency issues is not only database locking or optimistic concurrency.

It is:

Reducing simultaneous write contention

using:

  • Azure Service Bus Queue
  • Session-based ordering
  • Azure Functions
  • asynchronous processing
  • batch updates

This approach provides both:

  • scalability
  • consistency

which is extremely important for enterprise-grade Dataverse solutions

Comments

Popular posts from this blog

🔍 Dataverse + Azure Integration: Choosing Between Synapse Link and Microsoft Fabric

⚡ Example: Rate Limiting in Azure API Management