Transactions in Entity Framework

Posted on March 31, 2023
databaseEntity FrameworktransactionEntity Framework transactionsDatabase transactionsHandling exceptions during transactionsSaveChanges

What is a transaction in Entity Framework?

In Entity Framework, a transaction is a way to group a set of database operations into a single unit of work that must either succeed or fail as a whole. A transaction ensures that either all the operations in the group are committed to the database, or none of them are committed.

The source code for this article can be found on GitHub. Here source code of file

Default transaction behavior

By default, the entity framework supports transactions and these transactions are called implicit transactions, these are created automatically when all the changes call SaveChanges. A SaveChanges guarantees that either all the operations succeed or fail as a single unit.

For most applications, the default behavior is sufficient, you should only control transactions when it's really required.

await _dbContext.PingPong.AddAsync(new PingPong()
{
    Name = "Testing Default Transaction",
    CreatedDate = DateTime.Now,
    ModifiedDate = DateTime.Now,
    PublishedDate = DateTime.Now
}, cancellationToken);

await _dbContext.SaveChangesAsync();

How to manually create a transaction in Entity Framework?

Let's understand why it's required.

var manufacturerEntity = new Manufacturer()
{
    Name = "XYZ Model",
    Country = "India",
};
await _dbContext.Manufacturers.AddAsync(manufacturerEntity, cancellationToken);
await _dbContext.SaveChangesAsync();

await _dbContext.PingPong.AddAsync(new PingPong()
{
    Model = "Testing Default Transaction",
    ManufactureDate = DateTime.Now,
    ManufacturerId = manufacturerEntity.Id
}, cancellationToken);

await _dbContext.SaveChangesAsync();

In the example you provided, if an error occurs while inserting the PingPong entity and the transaction is rolled back, the Manufacturer entity that was added earlier will still be persisted in the database, resulting in unused data.

Here is a screenshot where you can see unused data.

unused data.

To avoid this, you can use a transaction to ensure that both entities are inserted or neither is inserted. Here's an updated example that uses a transaction:

using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
try
{
    var manufacturerEntity = new Manufacturer()
    {
        Name = "XYZ Model",
        Country = "India",
    };
    await _dbContext.Manufacturers.AddAsync(manufacturerEntity, cancellationToken);
    await _dbContext.SaveChangesAsync();

    var pingPongEntity = new PingPong()
    {
        Model = "Testing Default Transaction",
        ManufactureDate = DateTime.Now,
        ManufacturerId = manufacturerEntity.Id
    };
    await _dbContext.PingPong.AddAsync(pingPongEntity, cancellationToken);
    await _dbContext.SaveChangesAsync();

    await transaction.CommitAsync(cancellationToken);
}
catch (Exception ex)
{
    await transaction.RollbackAsync(cancellationToken);
    throw;
}

In this updated example, we first create a transaction using the BeginTransactionAsync method of the Database object. We then wrap both the Manufacturer and PingPong entity insertions inside the transaction.

If an exception occurs during the transaction, we roll back the transaction using the RollbackAsync method. If both entity insertions succeed, we commit the transaction using the CommitAsync method.

Using a transaction ensures that either both entities are inserted or neither is inserted, helping to maintain data integrity in the database.

What are Savepoints - Savepoints are markers that can be set within a transaction to allow for a partial rollback of the transaction. In a transaction, a savepoint can be created using the SAVEPOINT statement, which saves the current state of the transaction at that point. This allows for a partial rollback to the savepoint without having to undo the entire transaction.

Here is an updated example:

Save Point Example

In this example, a savepoint named "Manufacturer_Inserted" is created after the Manufacturer entity is inserted.

Inside the catch block, the RollbackToSavepointAsync method is called on the transaction object, passing in the name of the savepoint ("Manufacturer_Inserted"). This rolls back all changes made after the savepoint was created, effectively undoing the insertion of the PingPong entity.

Using a savepoint in this way allows us to roll back only part of a transaction, rather than the entire transaction. In this case, we can undo the insertion of the PingPong entity without undoing the insertion of the Manufacturer entity.

Why not use Entity Framework transaction

  1. Need for distributed transactions: If your application needs to perform transactions that involve multiple databases or resources, you may need to use distributed transactions. While EF supports distributed transactions, they can be complex to set up and manage.
  2. Performance concerns: Transactions can have an impact on application performance, especially if they involve a large number of entities or a high volume of data. In some cases, it may be more efficient to use low-level database transactions instead of EF transactions.
  3. Limited control over transaction boundaries: When using EF transactions, the boundaries of the transaction are determined by the DbContext's SaveChanges method. This may not always be the desired behavior, especially if you need more fine-grained control over the transaction boundaries.

The source code for this article can be found on GitHub. Here source code of file

Summary

A transaction is a way of grouping together a set of database operations that should be treated as a single unit of work. We discussed the default behavior of EF transactions, which is to use an implicit transaction for each database operation. This can result in multiple transactions being created for a single logical operation, leading to data consistency problems. Create transactions in EF using the TransactionScope class or the BeginTransaction method on the Database object. SaveChanges and SaveChangesAsync methods in EF, can be used to save changes to the database within a transaction. We talked about savepoints in SQL Server transactions, which allow you to create checkpoints within a transaction and roll back to a specific savepoint if an error occurs.

Thanks for reading!


Posted on March 31, 2023
Profile Picture

Arun Yadav

Software Architect | Full Stack Web Developer | Cloud/Containers

Subscribe
to our Newsletter

Signup for our weekly newsletter to get the latest news, articles and update in your inbox.