C#  

MSDTC in C#: Distributed Transaction Explained with Example

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 dcomcnfg tool).

  • 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

  1. When you open two SQL connections within the same TransactionScope, .NET automatically promotes the transaction to MSDTC.

  2. MSDTC coordinates the commit between both databases.

  3. If either cmd1 or cmd2 fails, 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

IssueSolution
“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
TimeoutSet custom timeout via TransactionOptions.Timeout.
Firewall issuesAllow 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 TransactionScope for 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.