<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: EveIsSim</title>
    <description>The latest articles on DEV Community by EveIsSim (@eveissim).</description>
    <link>https://dev.to/eveissim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2999732%2F1453cabc-0653-4e2e-92c8-3ae81650e7da.jpg</url>
      <title>DEV Community: EveIsSim</title>
      <link>https://dev.to/eveissim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eveissim"/>
    <language>en</language>
    <item>
      <title>C# How to ambient transaction work under the hood</title>
      <dc:creator>EveIsSim</dc:creator>
      <pubDate>Fri, 09 May 2025 08:00:00 +0000</pubDate>
      <link>https://dev.to/eveissim/how-to-ambient-transaction-work-under-the-hood-5e55</link>
      <guid>https://dev.to/eveissim/how-to-ambient-transaction-work-under-the-hood-5e55</guid>
      <description>&lt;p&gt;In &lt;a href="https://medium.com/@EveIsSIm/c-ambient-transactions-what-they-are-and-why-they-matter-c154caa8f27b" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, we looked at how  &lt;code&gt;TransactionScope&lt;/code&gt; (ambient transaction) simplifies transactional consistency. But how does it do this? How does such a simple and convenient mechanism solve this problem? How exactly is a transaction passed between threads is asynchronous communication? And how does the process of rollback and commit work?&lt;br&gt;
In this article we will try to figure it out with you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Structure:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;TransactionScope&lt;/code&gt; and &lt;code&gt;Transaction.Current&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How exactly does &lt;code&gt;ExecutionContext&lt;/code&gt; 'know' about &lt;code&gt;ContextKey&lt;/code&gt; through &lt;code&gt;AsyncLocal&lt;/code&gt; and what happens when &lt;code&gt;await&lt;/code&gt; occurs&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;Transaction&lt;/code&gt; is passed to &lt;code&gt;Npgsql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How do &lt;code&gt;Commit()&lt;/code&gt; and &lt;code&gt;Rollback()&lt;/code&gt; work&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  1.  TransactionScope and Transaction.Current
&lt;/h3&gt;

&lt;p&gt;sources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;transaction scope class - &lt;a href="https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs" rel="noopener noreferrer"&gt;source&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;System.Transaction class - &lt;a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Transactions.Local/src/System/Transactions/Transaction.cs" rel="noopener noreferrer"&gt;source&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;TransactionScope&lt;/code&gt; class in .NET is used to automatically manage transactions through the ambient transactions mechanism. You can read more about its application &lt;a href="https://medium.com/@EveIsSIm/c-ambient-transactions-what-they-are-and-why-they-matter-c154caa8f27b" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's try to figure out what happens when we create a &lt;code&gt;TransactionScope&lt;/code&gt;.&lt;br&gt;
Exp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TransactionScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// our repo methods.&lt;/span&gt;

&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For understanding, I suggest you open the &lt;code&gt;TransactionScope.cs&lt;/code&gt; source code in parallel - &lt;a href="https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs" rel="noopener noreferrer"&gt;source&lt;/a&gt;.&lt;br&gt;
NOTE: For easy of reading, find the creation of the &lt;code&gt;public TransactionScope(...)&lt;/code&gt; instance.&lt;br&gt;
At the time of writing this is line 53.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate and set &lt;code&gt;AsyncFlow&lt;/code&gt; in &lt;code&gt;ValidateAndSetAsyncFlowOption&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Trying to see if there is a transaction already created or if we have to create a new one in &lt;code&gt;NeedToCreateTransaction&lt;/code&gt; (the name might be  a bit weird, that's ok (-: )

&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;CommonInitialize&lt;/code&gt; method using &lt;code&gt;Transaction.GetCurrentTransactionAndScope(...)&lt;/code&gt; we try to get the current transaction and scope:

&lt;ol&gt;
&lt;li&gt;We &lt;strong&gt;look at the current transaction&lt;/strong&gt; in &lt;strong&gt;current thread&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;If there is one we return otherwise &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;ValidateAsyncFlowOptionAndESInteropOption&lt;/code&gt; 

&lt;ol&gt;
&lt;li&gt;In short, it is forbidden to use &lt;code&gt;AsyncFlow&lt;/code&gt; in single-threaded mode.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Check scope option, if &lt;code&gt;TransactionScope.Required&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;We return &lt;code&gt;false&lt;/code&gt; if there is a transaction, or &lt;code&gt;true&lt;/code&gt; if we need to create one.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Creation is done via &lt;code&gt;CommitableTransaction&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;This is the actual managed transaction object that will be either &lt;code&gt;Commit()&lt;/code&gt; or &lt;code&gt;Rollback()&lt;/code&gt; in the end.  (&lt;code&gt;TransactionScope&lt;/code&gt; automatically completes the transaction only if &lt;code&gt;Complete()&lt;/code&gt; has been called and there are no exceptions or other processed errors. Otherwise, the transaction is rolled back when leaving the using scope)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Make a &lt;code&gt;Clone()&lt;/code&gt; transaction for subsequent transfer to &lt;code&gt;Transaction.Current&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Why? The &lt;code&gt;Clone()&lt;/code&gt; method returns a transaction object without the ability to do &lt;code&gt;Commit()&lt;/code&gt;. Isolation and single point control mechanism.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;TransactionScope&lt;/code&gt; and specify the transaction as ambient available via &lt;code&gt;Transaction.Current&lt;/code&gt; in &lt;code&gt;PushScope()&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Here we are interested in the &lt;code&gt;CallContextCurrentData.CreateOrGetCurrentData&lt;/code&gt; method, it is operation is as follows:

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;AsyncLocal&amp;lt;ContextKey&amp;gt;&lt;/code&gt; - responsible for 'where we are'.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConditionalWeakTable&amp;lt;ContextKey, ContextData&lt;/code&gt; - stores the actual state of the transaction.&lt;/li&gt;
&lt;li&gt;=&amp;gt; we add our &lt;code&gt;ContextKey&lt;/code&gt; transaction on these objects.&lt;/li&gt;
&lt;li&gt;Now &lt;code&gt;ExecutionContext&lt;/code&gt; via &lt;code&gt;AsyncLocal&lt;/code&gt; 'knows' which &lt;code&gt;ContextKey&lt;/code&gt; is active and we can get the state from it.

&lt;ol&gt;
&lt;li&gt;See p.2 for exactly how this works.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SetCurrent&lt;/code&gt; - set the current transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  2. How exactly does &lt;code&gt;ExecutionContext&lt;/code&gt; 'know' about &lt;code&gt;ContextKey&lt;/code&gt; through &lt;code&gt;AsyncLocal&lt;/code&gt; and what happens when &lt;code&gt;await&lt;/code&gt; occurs
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ExecutionContext&lt;/code&gt; - This is the &lt;strong&gt;container of the logical execution context&lt;/strong&gt;, which includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AsyncLocal&amp;lt;T&amp;gt;&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CallContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SecurityContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and other 'logical' thread data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its main task:&lt;br&gt;
Automatically  &lt;strong&gt;copy and restore&lt;/strong&gt; all values associated with the current execution context when you switch between threads, tasks, etc.&lt;/p&gt;
&lt;h4&gt;
  
  
  Where is &lt;code&gt;AsyncLocal&lt;/code&gt; in this?
&lt;/h4&gt;

&lt;p&gt;Example from &lt;code&gt;Transaction.cs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContextKey&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s_currentTransaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContextKey&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s_currentTransaction.Value = someContextKey;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its mean, that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The current &lt;code&gt;ExecutionContext&lt;/code&gt; is saved&lt;/li&gt;
&lt;li&gt;Then, when continuing (on another thread/task), it will be restored&lt;/li&gt;
&lt;li&gt;And &lt;code&gt;s_currentTransaction.Value&lt;/code&gt; will be equal to  &lt;code&gt;someContextKey&lt;/code&gt; again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  What does &lt;code&gt;AsyncLocal&lt;/code&gt; do under the hood?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AsyncLocal&amp;lt;T&amp;gt;&lt;/code&gt; is registered in the &lt;code&gt;ExecutionContext&lt;/code&gt; via the .NET infrastructure&lt;/li&gt;
&lt;li&gt;When any &lt;code&gt;await&lt;/code&gt;, &lt;code&gt;ThreadPool.QueueUserWorkItem&lt;/code&gt;, &lt;code&gt;Task.Run()&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copying of ExecutionContext&lt;/strong&gt; occurs&lt;/li&gt;
&lt;li&gt;Along with it - all &lt;code&gt;AsyncLocal&lt;/code&gt; are copied&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. How &lt;code&gt;Transaction&lt;/code&gt; is passed to &lt;code&gt;Npgsql&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is where we have to touch on &lt;strong&gt;ADO.NET&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ADO.NET&lt;/code&gt; is a low-level data access platform in .NET that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow working with databases (SQL Server, PostgreSQL, Oracle, etc.)&lt;/li&gt;
&lt;li&gt;Supports connection, executing SQL queries, reading results, transaction
&lt;em&gt;read Microsoft documentation for more details&lt;/em&gt; - &lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-overview" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Npgsql&lt;/code&gt; is an &lt;code&gt;ADO.NET&lt;/code&gt; provider for PostgreSQL. In this article we will consider it, but the principle of transaction retrieval itself should be almost the same for other providers.&lt;/p&gt;

&lt;p&gt;When you work with &lt;code&gt;TransactionScope&lt;/code&gt; or &lt;code&gt;Transaction.Current&lt;/code&gt; and open &lt;code&gt;SqlConnection&lt;/code&gt;, &lt;code&gt;NpgsqlConnection&lt;/code&gt; and etc, the following happens:&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;Npgsql&lt;/code&gt; when you call &lt;code&gt;NpgsqlConnection.Open()&lt;/code&gt;, &lt;code&gt;Transaction.Current&lt;/code&gt; is checked, and it it is not null, the driver itself call &lt;code&gt;EnlistTransaction(...)&lt;/code&gt;, thus attaching the connection to the transaction.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: What happens if there is no transaction? - Then your request will work in normal &lt;code&gt;autocommit&lt;/code&gt; mode. So be careful when creating &lt;code&gt;TransactionScope&lt;/code&gt; to avoid making this situation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You call &lt;code&gt;async&lt;/code&gt; method inside &lt;code&gt;TransactionScope&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TransactionScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransactionScopeAsyncFlowOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoSmth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;

&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Inside the &lt;code&gt;_repo.DoSmth()&lt;/code&gt; method you open a connection to the database&lt;/li&gt;
&lt;li&gt;When the connection is opened, the binding to the transaction takes place:

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;NpgsqlConnection&lt;/code&gt; &lt;a href="https://github.com/npgsql/npgsql/blob/main/src/Npgsql/NpgsqlConnection.cs/" rel="noopener noreferrer"&gt;source&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;OpenAsync()&lt;/code&gt; method, we define &lt;code&gt;var enlistToTransaction = Settings.Enlist ? Transaction.Current : null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Settings.Enlist&lt;/code&gt; - answers whether to try to connect to the current transaction or not.

&lt;ol&gt;
&lt;li&gt;By default this value = &lt;code&gt;true&lt;/code&gt;. Documentation - &lt;a href="https://github.com/npgsql/npgsql/blob/main/src/Npgsql/NpgsqlConnectionStringBuilder.cs" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;=&amp;gt; we take &lt;code&gt;Transaction.Current&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;Where the next chain of calls will be:

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Transaction.Current =&amp;gt; GetCurrentTransactionAndScope(...) =&amp;gt; LookupContextData(...) =&amp;gt; TryGetCurrentData(...) =&amp;gt; s_currentTransaction.Value&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  FINISH: Congratulations, we have figured out how a transaction works within TransactionScope.
&lt;/h4&gt;

&lt;p&gt;But that is not all. Let's try to understand what &lt;code&gt;IDBTransaction&lt;/code&gt; has to do with it and how exactly &lt;code&gt;Commit&lt;/code&gt; and &lt;code&gt;Rollback&lt;/code&gt; will work. &lt;/p&gt;
&lt;h3&gt;
  
  
  4. How do Commit() and Rollback() work
&lt;/h3&gt;

&lt;p&gt;We have worked out that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Transaction.Current&lt;/code&gt; - is simply a global reference to the current active transaction in the thread or &lt;code&gt;AsyncLocal&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It does not commit or rollback&lt;/li&gt;
&lt;li&gt;It provides you a transaction that you can:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EnlistTransaction(tx)&lt;/code&gt; is a method that:

&lt;ul&gt;
&lt;li&gt;Remember &lt;code&gt;Transaction.Current&lt;/code&gt; as the active transaction&lt;/li&gt;
&lt;li&gt;Creates a real &lt;code&gt;BEGIN&lt;/code&gt; at the PostgreSQL level&lt;/li&gt;
&lt;li&gt;Waits for a &lt;code&gt;Commit()&lt;/code&gt; or &lt;code&gt;Rollback()&lt;/code&gt; command from .NET and does not do the &lt;code&gt;COMMIT&lt;/code&gt; itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Actually &lt;code&gt;Commit()&lt;/code&gt; is only called manually by the user via &lt;code&gt;scope.Complete()&lt;/code&gt;.&lt;br&gt;
If &lt;code&gt;scope.Complete()&lt;/code&gt; has not been called, then &lt;code&gt;Rollback()&lt;/code&gt; will be executed when &lt;code&gt;scope.Dispose()&lt;/code&gt; is called automatically or manually.&lt;/p&gt;
&lt;h4&gt;
  
  
  What does IDbTransaction have to do with this?
&lt;/h4&gt;

&lt;p&gt;In brief, it has nothing to do with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDbTransaction is an &lt;code&gt;ADO.NET&lt;/code&gt; interface for manual managing&lt;/li&gt;
&lt;li&gt;It allows you to manually manage a transaction&lt;/li&gt;
&lt;li&gt;has &lt;code&gt;Commit()&lt;/code&gt; and &lt;code&gt;Rollback()&lt;/code&gt; like &lt;code&gt;TransactionScope()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite this &lt;code&gt;TransactionScope&lt;/code&gt; and &lt;code&gt;IDBTransaction&lt;/code&gt; do not overlap directly, only relatively.&lt;/p&gt;
&lt;h4&gt;
  
  
  How then does &lt;code&gt;TransactionScope&lt;/code&gt; work without &lt;code&gt;IDBTransaction&lt;/code&gt;?
&lt;/h4&gt;

&lt;p&gt;When you use &lt;code&gt;TransactionScope&lt;/code&gt; with &lt;code&gt;NpgsqlConnection&lt;/code&gt; &lt;strong&gt;Npgsql registers a special &lt;code&gt;VilatileResourceManager&lt;/code&gt;&lt;/strong&gt; object in &lt;code&gt;Transaction.Current&lt;/code&gt; via the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnlistVolatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volatileResourceManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EnlistmentOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;VilatileResourceManager&lt;/code&gt; implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ISinglePhaseNotification&lt;/code&gt; (for an one-phase commit)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IEnlistmentNotification&lt;/code&gt; (because &lt;code&gt;ISinglePhaseNotification&lt;/code&gt; inherits it) (for a two-phase commit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we call &lt;code&gt;scope.Complete()&lt;/code&gt; = just sets the flag &lt;code&gt;_complete = true&lt;/code&gt;&lt;br&gt;
&lt;code&gt;scope.Dispose()&lt;/code&gt; =&amp;gt; call &lt;code&gt;Commit()&lt;/code&gt; from &lt;code&gt;CommitableTransaction&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;code&gt;resource.SinglePhaseCommit(enlistment)&lt;/code&gt; will subsequently be called.&lt;br&gt;
On the &lt;code&gt;Npgsql&lt;/code&gt; side, the &lt;code&gt;SinglePhaseCommit&lt;/code&gt; implementation will call &lt;code&gt;_connector.ExecuteInternal(“Commit”)&lt;/code&gt; where &lt;code&gt;_connector = NpgsqlConnector&lt;/code&gt;;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;NpgsqConnector&lt;/code&gt; itself does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formats the SQL-query into binary or text PostgreSQL protocol&lt;/li&gt;
&lt;li&gt;Sends the command via TCP (via &lt;code&gt;Socket&lt;/code&gt;, &lt;code&gt;Stream&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Processes the response (e.g., &lt;code&gt;CommandComplete&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Updates the internal transaction state in &lt;code&gt;connector.TrasactionStatus&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NOTE: This article only considers the single-phase commit scenario, where only one resource in invilved in a transaction.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;As a result, we have seen how exactly TransactionScope works under the hood and what makes it possible to write such simple and elegant code.&lt;/p&gt;

&lt;p&gt;Since this article serves as a companion to &lt;a href="https://medium.com/@EveIsSIm/c-ambient-transactions-what-they-are-and-why-they-matter-c154caa8f27b" rel="noopener noreferrer"&gt;C# Ambient Transactions: What They Are and Why They Matter&lt;/a&gt;&lt;br&gt;
I’ve added transaction logging at each step in the demo project, which you can see via the console. The code is &lt;a href="https://github.com/EveIsSim/patterns/tree/master/csharp/AmbientTransaction" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;console log exp.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Endpoint] Start: Thread: 7
[Endpoint] Start: Transaction.Current:
 =&amp;gt; [TxFactory] Creating TransactionScope
 =&amp;gt; [TxFactory] Thread: 7
 =&amp;gt; [TxFactory] TransactionScope created. Transaction.Current ID: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: 1. Before OpenAsync. Thread: 7
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: 1. Transaction.Current: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: UserId: 1. After OpenAsync. Still in Transaction: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: 2. Before OpenAsync. Thread: 7
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: 2. Transaction.Current: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] UpdateBalanca: UserId: UserId: 2. After OpenAsync. Still in Transaction: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] AddLog: UserFrom: 1 -&amp;gt; UserTo: 2. Before OpenAsync. Thread: 7
 =&amp;gt; =&amp;gt; [DB] AddLog: UserFrom: 1 -&amp;gt; UserTo: 2. Transaction.Current: c37ac104-0513-4445-b2cd-ae4687fb5598:1
 =&amp;gt; =&amp;gt; [DB] AddLog: UserFrom: 1 -&amp;gt; UserTo: 2. After OpenAsync. Still in Transaction: c37ac104-0513-4445-b2cd-ae4687fb5598:1
[Endpoint] Stop: After [TxFactory].Dispose: Thread: 7
[Endpoint] Stop: After [TxFactory].Dispose: Transaction.Current:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for your attention.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>database</category>
      <category>dotnet</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>C# Ambient Transactions: What They Are and Why They Matter</title>
      <dc:creator>EveIsSim</dc:creator>
      <pubDate>Mon, 31 Mar 2025 23:05:20 +0000</pubDate>
      <link>https://dev.to/eveissim/c-ambient-transactions-what-they-are-and-why-they-matter-5c9k</link>
      <guid>https://dev.to/eveissim/c-ambient-transactions-what-they-are-and-why-they-matter-5c9k</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Transactions are the main mechanism for ensuring data consistency in a system. A transaction ensures that all actions within it either succeed or fail, keeping the system in a consistent state.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;.NET&lt;/code&gt;, transactions can be managed manually using &lt;code&gt;BeginTransaction()&lt;/code&gt; from &lt;code&gt;System.Data.IDbConnection&lt;/code&gt;. Many developers, both experienced and novice, choose this approach.&lt;/p&gt;

&lt;p&gt;However, manually managing transactions creates many problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forgotten commits&lt;/li&gt;
&lt;li&gt;Unhandled exceptions&lt;/li&gt;
&lt;li&gt;Nesting conflicts&lt;/li&gt;
&lt;li&gt;Rollback errors&lt;/li&gt;
&lt;li&gt;Generating &lt;code&gt;Leaky repository&lt;/code&gt; OR &lt;code&gt;Separation of Concerns&lt;/code&gt;. When business logic goes beyond the &lt;em&gt;Business layer&lt;/em&gt; and spreads to the &lt;em&gt;Repository layer&lt;/em&gt; and vice versa.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;async/await&lt;/code&gt; issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is especially painful when the code base starts to grow and grow dependencies and duplicated parts, violating the most important things in the project: &lt;em&gt;readability, support, testing&lt;/em&gt;, in other words, violating the &lt;code&gt;SRP&lt;/code&gt; principle.&lt;/p&gt;

&lt;p&gt;In this article, I will talk about typical problems of using &lt;code&gt;BeginTransaction()&lt;/code&gt; and how they can be solved via &lt;code&gt;TransactionScopeFactory&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Forgotten commit or rollback
&lt;/h3&gt;

&lt;p&gt;One of the most frequent errors is a forgotten &lt;code&gt;Commit()&lt;/code&gt; or &lt;code&gt;Rollback()&lt;/code&gt;. It can be happen if the code has conditional branching, early &lt;code&gt;return&lt;/code&gt;. Or an exception is thrown.&lt;/p&gt;

&lt;p&gt;As result, the transaction hangs, blocks resources, or results in data loss.&lt;/p&gt;

&lt;p&gt;In addition, &lt;code&gt;Rollback()&lt;/code&gt; can throw an exception too (e.g. for connection problems), and if it is not handled, the application may crash at the moment when it was supposed to be recovering.&lt;/p&gt;




&lt;h3&gt;
  
  
  Non-processing of exceptions
&lt;/h3&gt;

&lt;p&gt;When working with transactions, you should always wrap a block of operations in &lt;code&gt;try/catch&lt;/code&gt;. But this is often forgotten, especially in a hurry or when it seems “nothing can do wrong”.&lt;/p&gt;

&lt;p&gt;This results in unclosed transactions, connection leaks and data loss.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conflicts with nesting
&lt;/h3&gt;

&lt;p&gt;Manual transactions do not support nesting. If an internal methods starts &lt;code&gt;BeginTransaction&lt;/code&gt; without knowing that a transaction has already been started above, an error may occur: either an exception or the logic will start working in a new isolated transaction, breaking atomicity.&lt;/p&gt;

&lt;p&gt;This is especially critical when reusing libraries.&lt;/p&gt;

&lt;p&gt;The principle of trust in previously written code by your teammates is violated.&lt;/p&gt;




&lt;h3&gt;
  
  
  Leaky Repository / Violation of Separation of Concerns
&lt;/h3&gt;

&lt;p&gt;When you use &lt;code&gt;BeginTransaction&lt;/code&gt; in business logic and commit in repository, or vice versa, you are blurring the responsibility between the layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Service  &lt;/span&gt;
&lt;span class="n"&gt;_repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DoSomething&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

&lt;span class="c1"&gt;// Repository  &lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// Additional logic related to business or rollback logic.  &lt;/span&gt;
&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This violates &lt;code&gt;Separation of Concerns&lt;/code&gt;: now the service must know that repository itself commits something, and the repository cannot be reused in other contexts.&lt;/p&gt;

&lt;p&gt;This kind of code is hard to test, scale, and maintain.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/" rel="noopener noreferrer"&gt;leaky abstraction&lt;/a&gt;, and one of the main anti-patterns when working with transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems with &lt;code&gt;async/await&lt;/code&gt; and explicit transaction passing
&lt;/h3&gt;

&lt;p&gt;When you use &lt;code&gt;BeginTransaction&lt;/code&gt; in asynchronous code, it becomes necessary to explicitly pass the transaction object &lt;code&gt;IDbTransaction&lt;/code&gt; and the connection &lt;code&gt;IDbConnection&lt;/code&gt; to all methods that participate in the transition.&lt;/p&gt;

&lt;p&gt;This make the code less readable, more brittle, and breaks encapsulation.&lt;/p&gt;

&lt;p&gt;Each method must accept both the connection and the transaction, even if it does not directly deal with them itself. This leads to strong coupling, makes testing harder, and makes the code more vulnerable to bugs. One forgotten &lt;code&gt;tx&lt;/code&gt; in the parameters and the logic goes beyond the transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: How to solve these problems and what does TransactionScopeFactory have to do with it?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TransactionScopeFactory&lt;/code&gt; is a wrapper that helps to create a &lt;code&gt;TransactionScope&lt;/code&gt; with the desired settings, while taking into account the current &lt;code&gt;ambient&lt;/code&gt; context and working correctly with &lt;code&gt;async/await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As examples we will consider a simplified system of recording money transfers from customer &lt;code&gt;A =&amp;gt; B&lt;/code&gt; and logging the fact the transfer was made.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Service&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;...&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(....)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;.....&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;.....&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_transactionScopeFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateBalance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLog&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;.....&lt;/span&gt;

&lt;span class="c1"&gt;// Repository&lt;/span&gt;


&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountEntity&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;UpdateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSucces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;UpdateBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountEntity&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"UPDATE accounts SET ...."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;ExecuteAsync&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AddLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransactionLogEntity&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
            INSERT INTO ....
            VALUES ...."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;ExecuteAsync&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allows you to get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean and readable code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;try/catch&lt;/code&gt; processing is at the repository level. All business logic is in one place: clear and easy to understand.&lt;/li&gt;
&lt;li&gt;Repository methods are concise and simple and ready to reused&lt;/li&gt;
&lt;li&gt;Support for &lt;code&gt;async/await&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Secure transaction completion&lt;/li&gt;
&lt;li&gt;Respect for external &lt;code&gt;ambient&lt;/code&gt; — context if it already exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to implement TransactionScopeFactory
&lt;/h3&gt;

&lt;p&gt;If you want to apply this approach to you project, below is a basic example of a &lt;code&gt;TransactionScopeFactory&lt;/code&gt; implementation.&lt;/p&gt;

&lt;p&gt;A more detailed implementation with integration into business logic, repositories and &lt;code&gt;unit-of-work&lt;/code&gt; can be viewed in my repository on GitHub: &lt;a href="https://github.com/EveIsSim/patterns/tree/master/csharp/AmbientTransaction" rel="noopener noreferrer"&gt;EveIsSim/AmbientTransaction&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create interface:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ITransactionScopeFactory&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;TransactionScope&lt;/span&gt; &lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add implementation:
where:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TransactionScopeOption.Required&lt;/code&gt; — If there is already an active transaction in the current context — join it. If not — create a new one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IsolationLevel.ReadCommitted&lt;/code&gt; — It provides protection against “dirty reads” and is a reasonable compromise between security and perfomance.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TransactionScopeAsyncFlowOption.Enabled&lt;/code&gt; — If you specify this flag when creating a TransactionScope, &lt;code&gt;Transaction.Current&lt;/code&gt; will be saved via await.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionScopeFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITransactionScopeFactory&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TransactionScope&lt;/span&gt; &lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TransactionScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;TransactionScopeOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TransactionOptions&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;IsolationLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IsolationLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadCommitted&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;TransactionScopeAsyncFlowOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Register with DI and use throughout the project.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you are still manually managing transactions via &lt;code&gt;BeginTransaction&lt;/code&gt;, you have probably already experienced the pain of supporting such code. An abstraction like &lt;code&gt;TransactionScopeFactory&lt;/code&gt; helps you write cleaner, more stable and reusable code. This is especially important when a project grows and dozens of methods and services start touching transactions.&lt;/p&gt;

&lt;p&gt;Take advantage of the convenience of the C# language. (=&lt;/p&gt;

&lt;p&gt;Thank you for your attention.&lt;/p&gt;

&lt;p&gt;EveIsSim (everything is simple)&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently asked question: Is await using for TransactionScope necessary?
&lt;/h3&gt;

&lt;p&gt;The short answer is no, you do not need it.&lt;/p&gt;

&lt;p&gt;Although &lt;code&gt;TransationScope&lt;/code&gt; supports &lt;code&gt;async/await&lt;/code&gt; via &lt;code&gt;TransactionScopeAsyncFlowOption.Enabled&lt;/code&gt;, it does not itself implement the &lt;code&gt;IAsyncDisposable&lt;/code&gt; interface, and thus does not require &lt;code&gt;await using&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why does the confusion arise?&lt;/p&gt;

&lt;p&gt;Many people assume that supporting &lt;code&gt;async&lt;/code&gt; = the need for &lt;code&gt;DisposeAsync()&lt;/code&gt;, but &lt;code&gt;TransactionScope&lt;/code&gt; uses regular &lt;code&gt;Dispose()&lt;/code&gt; even in an asynchronous context.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await using&lt;/code&gt; is only required when the object implements &lt;code&gt;IAsyncDisposable&lt;/code&gt;. This is not the case.&lt;/p&gt;

&lt;p&gt;When then is &lt;code&gt;await using&lt;/code&gt; appropriate?&lt;/p&gt;

&lt;p&gt;If you wrap &lt;code&gt;TransactionScope&lt;/code&gt; in its own wrapper that implements &lt;code&gt;IAsyncDisposable&lt;/code&gt;— then &lt;code&gt;await using&lt;/code&gt; makes sense.&lt;/p&gt;

&lt;p&gt;So, if you use &lt;code&gt;TransactionScope&lt;/code&gt; directly — you can and should get by with just &lt;code&gt;using&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>backend</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
