When your .NET application needs to perform operations across multiple resources (for example, two SQL Server databases, MSMQ, or another transactional resource) and you want them all to succeed or fail together, you can use MSDTC (Microsoft Distributed Transaction Coordinator).
Key idea: MSDTC ensures atomicity across multiple systems. If one operation fails, the transaction coordinator rolls back all other operations automatically.
đź’ˇ What is MSDTC?
The Microsoft Distributed Transaction Coordinator (MSDTC) is a Windows service that coordinates transactions that span multiple resource managers, such as:
- Multiple SQL Server databases 
- SQL Server + Message Queue (MSMQ) 
- SQL Server + Oracle + File System (through transactional NTFS, historically) 
đź”— How It Works
When you use TransactionScope in .NET and your code accesses more than one transactional resource, the .NET Framework automatically promotes the transaction to a distributed transaction.
The transaction coordinator (MSDTC) then ensures that:
- All resources either commit or roll back. 
- If any resource fails, MSDTC sends rollback commands to others. 
- It guarantees two-phase commit (2PC) consistency. 
⚙️ Prerequisites
- MSDTC service must be running on all participating servers. 
- Network DTC Access should be enabled (via - dcomcnfgtool).
 
- SQL Server must allow distributed transactions. 
// To check MSDTC service
services.msc
// or from cmd
net start msdtc
đź§© Example 1: Distributed Transaction with Two SQL Databases
using System;
using System.Data.SqlClient;
using System.Transactions;
class Program
{
    static void Main()
    {
        string connStr1 = "Data Source=SQLSERVER1;Initial Catalog=DbA;Integrated Security=True;";
        string connStr2 = "Data Source=SQLSERVER2;Initial Catalog=DbB;Integrated Security=True;";
        using (var scope = new TransactionScope())
        {
            using (var conn1 = new SqlConnection(connStr1))
            using (var conn2 = new SqlConnection(connStr2))
            {
                conn1.Open();
                conn2.Open();
                var cmd1 = new SqlCommand("INSERT INTO Orders (OrderId, Amount) VALUES (1, 500)", conn1);
                var cmd2 = new SqlCommand("INSERT INTO AuditLog (LogId, Description) VALUES (1, 'Order created')", conn2);
                cmd1.ExecuteNonQuery();
                cmd2.ExecuteNonQuery();
            }
            // If we reach here, both commands succeeded
            scope.Complete();
        }
        Console.WriteLine("âś… Both databases committed successfully!");
    }
}
What happens
- When you open two SQL connections within the same - TransactionScope, .NET automatically promotes the transaction to MSDTC.
 
- MSDTC coordinates the commit between both databases. 
- If either - cmd1or- cmd2fails, the whole transaction rolls back.
 
Important: If you open only one connection, the transaction remains local and MSDTC is not used.
đź§  Example 2: Distributed Transaction in Entity Framework
using (var scope = new TransactionScope())
{
    using (var db1 = new SalesDbContext())
    {
        db1.Orders.Add(new Order { OrderId = 100, Amount = 1200 });
        db1.SaveChanges();
    }
    using (var db2 = new AuditDbContext())
    {
        db2.AuditLogs.Add(new AuditLog { LogId = 100, Description = "Order 100 created" });
        db2.SaveChanges();
    }
    // Commit only if both succeed
    scope.Complete();
}
Entity Framework internally uses ADO.NET connections, so MSDTC is automatically involved when multiple connections are opened within a TransactionScope.
🛠️ Troubleshooting MSDTC
| Issue | Solution | 
|---|
| “MSDTC not available” | Start the service: net start msdtc | 
| “Transaction manager has disabled its support for remote/network transactions” | Enable Network DTC Access in dcomcnfg→ Component Services → Computers → My Computer → Distributed Transaction Coordinator → Properties → Security | 
| Timeout | Set custom timeout via TransactionOptions.Timeout. | 
| Firewall issues | Allow DTC TCP port (default: 135) and dynamic ports. | 
⚖️ TransactionScope Options
var options = new TransactionOptions
{
    IsolationLevel = IsolationLevel.ReadCommitted,
    Timeout = TimeSpan.FromMinutes(2)
};
using (var scope = new TransactionScope(
    TransactionScopeOption.Required, options))
{
    // Your code here
}
- TransactionScopeOption.Required: Join an existing transaction or create a new one. 
- TransactionScopeOption.RequiresNew: Always create a new transaction. 
- TransactionScopeOption.Suppress: Run outside any transaction. 
đźš« When Not to Use MSDTC
MSDTC is powerful but not suitable for all scenarios:
- It adds overhead and complexity for remote coordination. 
- Azure SQL Database and Azure Blob Storage do not support distributed transactions. 
- For cloud-native applications, use the Saga Pattern or Eventual Consistency instead. 
âś… Best Practices
- Keep transactions short-lived to avoid locks. 
- Use a single - TransactionScopefor related operations.
 
- Do not open unnecessary connections inside a distributed transaction. 
- Enable MSDTC logging for monitoring commits and rollbacks. 
Summary
MSDTC provides guaranteed atomicity for operations involving multiple transactional resources. In modern .NET systems, use it when working across multiple SQL databases or legacy systems on Windows environments. For cloud or microservices, prefer patterns like the Saga or Outbox pattern.