0

If I'm in the middle of doing a bunch of updates within a transaction, and it throws an exception, I still want to log some information in a table in the same db context so I know what happened. I mistakenly thought, per this answer, that all I needed to do was create a new instance of the context and those db inserts would be separate from the transaction. However they too are being rolled back. And I'm not sure if it is because I am missing the new Database() wrapper, but it won't let me create this statement as it seems the Database class doesn't have a public constructor that allows this.

Then I saw this answer which likely explains why. And I could see that you may want to have multiple contexts included in the transaction.

So how can I, within a transaction, exclude some db inserts from that transaction so they are not rolled back?

This is an overly simplified example. In reality, the TransactionScope() is created at the top level in the controller, and the try{} occurs many method calls deep.

var contextA = new ContextA();
using (var scope = new TransactionScope())
{
    // Log Errors in DB with new instance of same context
    var contextAforLogging = new ContextA()
    
    // Save entity in context A
    contextA.Save(...);
    
    //log step
    contextAforLogging.Save(...);
    contextAforLogging.SaveChanges();

    // More updates to original entity in context A
    contextA.Save(...);
    
    //log step
    contextAforLogging.Save(...);
    contextAforLogging.SaveChanges();
    
    // Commit tx-scope
    scope.Complete();          
}
catch (Exception ex) {
   return....;                        
}
     
4
  • You can create savepoints and perhaps rollback them on error and then Commit the logging part. Of course this doesn't work too well if you have transaction abortive errors. Alternatively is to log outside of transaction altogether. Although, i dunno how any of this translates into EF code Commented Aug 21, 2024 at 21:35
  • @siggemannen I still want to rollback the entire set up updates but would like to know what happened along the way when it rolls back. Because this logic is 8 or so method calls deep, and to make it easier for the user to see the steps that occurred along the way, I wanted to make these logs along the way. I could capture all these results in a string[] and pass them all the way back up the chain and then log them outside of the transaction, but that will take a decent amount of refactoring and complexity I was hoping to avoid if at all possible. Commented Aug 21, 2024 at 21:39
  • Have you considered a separate context instance and scope with TransactionScope.Suppress? It might be a lot cleaner if you isolated logging to a separate singleton or service. Commented Aug 21, 2024 at 21:50
  • @AlwaysLearning Ah! I was not even aware this existed. In researching this concept, I see my same question here link Commented Aug 22, 2024 at 0:28

1 Answer 1

1

Firstly, check that you are using the right tool for the job. Are you using a transaction to coordinate database inserts/updates using EF with DbContexts across different database connections or other transaction-supporting services? If "Yes" then TransactionScope is warranted. If "No" then are you coordinating database inserts/updates across multiple EF DbContexts using the same database connection? If "Yes" consider using:

using var transaction = context1.BeginTransaction();  
context2.Database.UseTransaction(transaction.GetDbTransaction());

If "No" then you most likely do not need an explicit transaction. EF already wraps operations in a transaction and leveraging navigation properties for inserting related entities as an aggregate root will see things like FKs set automatically. Any fully separate entities will also all be saved within the scope of a transaction when SaveChanges is called.

If you do need a TransactionScope then your logging code can opt out of a transaction scope. I'd recommend using a Log() method to encapsulate this so it isn't missed somewhere:

private void Log(string message)
{
    using var txScope = new TransactionScope(TransactionScopeOption.Suppress));
    using var contextForLogging = new AppDbContext(); // or better a DbContextFactory.Create()
//log step

    var logEntry = new LogEntry(message);
    contextForLogging.Logs.Add(logEntry);
    contextforLogging.SaveChanges();
    txScope.Commit();
}

Ideally when processing work with a single DbContext you should avoid calling SaveChanges() multiple times. This is usually a sign that you are neglecting navigation properties and trying to manage relationships between entities manually where EF supports that entirely behind the scenes.

Sign up to request clarification or add additional context in comments.

2 Comments

Just many updates in the same context. So are you saying if you don't call context.SaveChanges() until after all dbchanges are made and then call it only once at the very end, then EF will wrap that in a transaction automatically so if something fails, all the updates roll back, right? Assuming so, interesting...I learned something new.
Yes. Performing things like "Add" don't create records in the DB until SaveChanges(), the change tracking tracks all inserts, updates, and deletes you are making where SaveChanges creates a transaction executes the changes, and then commits/rolls back. Navigation properties help manage things like FKs automatically, no need to insert, save, get ID to insert related. Parent entity has a collection of children populated and when SaveChanges inserts them in order and populates the FKs all automatically. Issues usually start when trying to manually do what it wants to handle automatically.:)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.