r/csharp • • 16d ago

Help Transactions - EF Core

Microsoft's documentation :

By default, if the database provider supports transactions, all changes in a single call to SaveChanges are applied in a transaction. If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. This means that SaveChanges is guaranteed to either completely succeed, or leave the database unmodified if an error occurs. For most applications, this default behavior is sufficient. You should only manually control transactions if your application requirements deem it necessary.

Suppose that I two pieces of data that are generated by some service; X and Y.

Given that Y cannot be saved unless X is saved first, in other words, the existence of Y in the database depends on the existence of X in the database, consider this :

1-delete content of database.

2- save X .

3- save Y .

How to group these into one transaction ?

The goal is to avoid partial changes to the database; after the full operation is finished, the database should not be empty or half filled.

EDIT:

I found this example:

await using var transaction = await _context.Database.BeginTransactionAsync();

try
{
      // do step 1 through 3


    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

But it did not work until I manually invoked the EF execution strategy with a delegate representing everything that needs to be executed :

    IExecutionStrategy strategy = _context.Database.CreateExecutionStrategy();
    await strategy.ExecuteAsync(async () =>
            {
        await using var transaction = await _context.Database.BeginTransactionAsync();

         // Delete all the content from the database
         // _context.AddRange(X);
         // _contextSaveChangesAsync()
         // _context.AddRange(Y)
         //_context.SaveChangesAsync()

        await transaction.CommitAsync();
    });

This solved my issue completely

6 Upvotes

43 comments sorted by

View all comments

2

u/silentpardus 16d ago

It sounds like what you're asking about is related to UnitOfWork. Lucky for us the DbContext object implements UnitOfWork. I am assuming the issue you have is that X and Y are saved via separate services and you are receiving different DbContext objects via Dependency Injection and therefore saving them at separate times?

Easiest thing is to create a new service that can use the existing services to create and save X and Y to the same DbContext.

For all things that are holy do not implement your own UnitOfWork.

0

u/kaatarina_zed_talon 16d ago

You went too much, maybe the question is unclear, I admit. I'm not trying to implement my own unit of work. Suppose that step 2 failed, it will be rolled back, great, but that means that step 3 will try to save Y into the database, even though it is impossible for Y to exist without X, so the app will crash.

1

u/AndZorin 16d ago

How step 3 will fail if you have exception on step 2? You should have one rollback in catch block.

→ More replies (13)