Sometimes applications have to perform multiple operations on the database. Traditionally, database level transactions are used by applications in order to ensure atomicity of such operations.
In this article, let’s see how the transactions can be used when the data access layer is built using .NET EF Core.
Save Changes and Transactions
As we know, EF core context tracks the entities. All the operations performed on the entities are performed on in-memory objects. After performing the operations when SaveChanges or SaveChangesAsync is called, all those changes are stored in the underlying database. This operation is by default atomic.
This means if any of the operations performed on in memory objects fail during execute of SaveChanges or SaveChangesAsync, all the changes are rolled back. Hence, the database is guaranteed to be updated by all operations or database would remain unmodified due to failure of one or more operations.
Many real world applications can rely on this default behavior. Hence there is no need of explicitly initiating transactions for such applications.
Using Transactions
If due to any reasons, default behavior is not sufficient, then transactions can be started explicitly. Let’s say an operation requires multiple calls to SaveChanges or SaveChangesAsync method. In such case, the DbContext.Database instance can be used to begin and end the transactions as shown in below code snippet.
The methods used in below code snippet BeginTransaction and RollbackTransaction also have async
implementations.
static async Task Main(string[] args) { AddStudents(); } static void AddStudents() { using var context = BuildUniversityContext(); using var transaction = context.Database.BeginTransaction(); try { context.Add( new Student { FirstName = "John", LastName = "Doe", Address = "4 Privet Drive", }); context.SaveChanges(); context.Add( new Student { FirstName = "Jane", LastName = "Doe", Address = "4 Privet Drive", }); context.SaveChanges(); transaction.Commit(); } catch(Exception ex) { transaction.Rollback(); // Other steps for handling failures } }
Note that this type of transaction would work only if the multiple save changes operations were done on a single DbContext. If the save changes are performed on multiple DbContext instances, then this type of transactions would not work. Cross-Context transactions are not converted in this article. Maybe, I will try to write about them in coming articles.
Savepoints
Within a given transaction, when SaveChanges operations performed multiple times, EF core automatically creates a savepoint
for every SaveChanges call.
If SaveChanges fail due to any reason, the transaction can be rolled back until last successful Savepoint
. After coming to last savepoint
, the transaction is in the same state as if the remaining part of transactions has not been executed yet.
It is also possible to create and name the savepoints
manually. In this way, a transaction can be rolled back to one of the save points. Below code example shows how the transaction can be rolled back to a specific save point.
The methods used in below code snippet – CreateSavepoint and RollbackToSavepoint – also have async
implementations
static async Task Main(string[] args) { AddStudents(); } static void AddStudents() { using var context = BuildUniversityContext(); using var transaction = context.Database.BeginTransaction(); try { context.Add( new Student { FirstName = "John", LastName = "Doe", Address = "4 Privet Drive", }); context.SaveChanges(); transaction.CreateSavepoint("FirstUserAdded"); context.Add( new Student { FirstName = "Jane", LastName = "Doe", Address = "4 Privet Drive", }); context.SaveChanges(); transaction.Commit(); } catch(Exception ex) { transaction.RollbackToSavepoint("FirstUserAdded"); } }
Considerations
Some databases may not provide support for transactions. In such cases, such databases may throw exceptions in case transactions APIs are called.
Also, the save point feature may not be supported by many databases. In such cases, an error in save changes may leave transactions in an unknown state. Hence, it should be analyzed carefully to ensure that the features are supported by underlying database in order to avoid any issues.
Andriy Kravets is writer and experience .NET developer and like .NET for regular development. He likes to build cross-platform libraries/software with .NET.