Dataverse plug-ins run within a managed transaction, ensuring data consistency and rollback capabilities. However, attempting to manually create a transaction inside a plug-in can cause the following error:
🚨 Error Message:
Error Code: -2147220989
Error Message: You cannot start a transaction with a different isolation level than is already set on the current transaction.
This error occurs when your plug-in tries to initiate a new database transaction while already operating within an existing one.
In this blog, we’ll explore why this happens and how to properly handle database operations within plug-ins.
🔍 Why Does This Error Occur?
This error typically occurs when:
1️⃣ Explicitly Creating a New Transaction Inside a Plug-in
- Using
TransactionScopeor a direct SQLBEGIN TRANSACTIONin a plug-in is unnecessary and conflicts with Dataverse’s internal transaction.
2️⃣ Calling Another Plug-in That Tries to Start a Transaction
- If a parent plug-in calls another plug-in that also attempts to manage a transaction, it results in a conflict.
3️⃣ Using External Database Transactions Within Dataverse Plug-ins
- Direct SQL operations (e.g., via an Azure Function or External API) that attempt to start their own transactions may cause this issue.
4️⃣ Performing Metadata Changes in a Synchronous Plug-in
- Schema modifications (e.g., adding columns or updating relationships) are not supported in synchronous plug-ins because they inherently require transactions.
🔧 How to Fix This Error
1️⃣ Avoid Creating New Transactions in Plug-ins
Dataverse already manages transactions for you, so avoid using TransactionScope in your plug-in code.
❌ Incorrect Code (Causes Error)
using (TransactionScope scope = new TransactionScope())
{
service.Create(new Entity("account") { ["name"] = "New Account" });
scope.Complete();
}
This conflicts with the existing Dataverse transaction and leads to the error.
✅ Correct Approach (Let Dataverse Handle Transactions)
service.Create(new Entity("account") { ["name"] = "New Account" });
Simply use IOrganizationService for database operations—Dataverse ensures transaction integrity automatically.
2️⃣ Use Asynchronous Plug-ins for Long-Running Transactions
If your operation involves multiple steps or external data sources, consider asynchronous plug-ins to avoid transaction conflicts.
📌 Steps to Convert a Plug-in to Async:
- In Plug-in Registration Tool, set the Execution Mode to Asynchronous instead of Synchronous.
- Remove any explicit transaction handling in your code.
3️⃣ Avoid Schema Modifications in Plug-ins
Metadata changes (e.g., adding tables, updating fields) should not be done inside synchronous plug-ins. Instead:
✔ Use Power Automate or Azure Functions for metadata operations.
✔ If necessary, perform metadata changes asynchronously.
4️⃣ Handle External API Calls Without Transactions
If your plug-in interacts with an external API that performs database updates, ensure the API does not start a new transaction.
✅ Best Practice: Use stateless APIs that accept data without managing transactions.
📌 Best Practices to Avoid This Error
✔ Never use TransactionScope or BEGIN TRANSACTION inside a plug-in.
✔ Use asynchronous execution for complex operations.
✔ Avoid schema modifications inside plug-ins.
✔ Ensure external API calls don’t start new transactions.
📢 Coming Up Next…
In Blog 10, we’ll discuss a known issue with Activity.RegardingObjectId, explaining why the Regarding field sometimes appears blank and how to ensure correct lookup behavior in plug-ins.
Stay tuned for another troubleshooting deep dive! 🚀
