0

I'm facing following problem: In my project, I have done error logging into the same DB, as the application is using. That means, that if error occurs, then in each catch, there is the error stored into DB. The problem is however, when using transactions. When error occurs, the tran rollbacks, but it also rollbacks the error logged, like in this scenario:

this is public service, used to save client changes.

public UpdateClient(client)
{
    try
    {
        TransactionScope scope0 = new TransactionScope();

        // some code

        scope0.Complete();
    }
    catch(Exception ex)
    {
        Logger.LogException(ex); //log the exception into DB
    }    
}

this is public service, used to multiple clients changes:

 public void UpdateClients(clients)
    {
        try
        {
            TransactionScope scope1 = new TransactionScope();
            foreach (client c in clients)
                {
                    UpdateClient(c);
                }
             scope1.Complete();
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);           
        }
    }

What happend here is, that if using UpdateClients(), and if error occurs in UpdateClient(), then it is logged into DB in UpdateClient() catch block. The scope0 is rollbacked, and doesnt affect the logged exception. But the scope1 is also rollbacked, and will rollback also the exception stored in DB in the UpdateClient() catch block.

I know, that there are options like store the errors in different DB and so on, but this is unfortunately not acceptible in current state of development. Is there any way, how to solve this problem without major changes?

niio
  • 326
  • 3
  • 15
  • Possible duplicate of [Nested/Child TransactionScope Rollback](http://stackoverflow.com/questions/2741988/nested-child-transactionscope-rollback) –  Nov 23 '15 at 08:57
  • You may collect the error messages and insert them into the database after the rollback. – René Vogt Nov 23 '15 at 09:24
  • @RenéVogt Could you be more specific, how to collect them and distinguish between direct call of UpdateClient() and call from UpdateClients()? – niio Nov 23 '15 at 09:26

2 Answers2

0

You can collect the exceptions while updating and insert them into the db outside the transaction scope:

public UpdateClient(client, bool logErrors = true)
{
  try
  {
    TransactionScope scope0 = new TransactionScope();

    // some code

    scope0.Complete();
  }
  catch(Exception ex)
  {
    Logger.EnlistException(ex); // collect the exception
  }
  if (logErrors) Logger.WriteEnlistedExceptions();    
}

public void UpdateClients(clients)
{
  try
  {
     TransactionScope scope1 = new TransactionScope();
     foreach (client c in clients)
     {
       UpdateClient(c, false);
     }
     scope1.Complete();
  }
  catch (Exception ex)
  {
      Logger.EnlistException(ex);           
  }
  Logger.WriteEnlistedExceptions();
}

public partial class Logger
{
  private static List<Exception> _exceptionList = new List<Exception>();
  public static void EnlistException(Exception ex)
  {
    _exceptionList.Add(ex);
  }
  public static void WriteEnlistedExceptions()
  {
    foreach(Exception ex in _exceptionList)
      LogException(ex);
    _exceptionList.Clear();
  }
}

This is of course not yet thread safe. The distinction between a call from UpdateClient or UpdateClients can be made by the stack trace of the exception.

As the answers to the question linked by Micky show, there is no way to prevent the rollback by the outer scope.

[EDIT] added logErrors flag

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • Ah, sorry, now I get your point. But on the other hand: is the outer transaction scope really necessary? Why roll back all clients if only one could not be updated? – René Vogt Nov 23 '15 at 09:51
  • But when you call UpdateClient directly, as this is public, you will just enlist the exception, but never log, or am I wrong? – niio Nov 23 '15 at 09:55
  • the outer tran is necessary, the bisnis case is different from what I have post here. This question is just simplified problem. – niio Nov 23 '15 at 09:57
  • Edited the answer and added a `logErrors` parameter to `UpdateClients` method with `true` as default value. – René Vogt Nov 23 '15 at 10:22
  • thanks for providing possible solution, I'll try to discus this with other colleagues on project, but the flag won't be probably accepted. – niio Nov 23 '15 at 11:50
0

So I found an acceptable solution. The code will be implemented in internal method without transaction and will enlist exception in catch. Then usage is as follows:

internal UpdateClient(client)
{
try
  {
    // some code
  }
  catch(Exception ex)
  {
    Logger.EnlistException(ex); // collect the exception
  }
}

public UpdateClient(client)
{
  try
  {
    TransactionScope scope0 = new TransactionScope();

    //call internal UpdateClient
    /*internal*/ UpdateClient(client);

    scope0.Complete();
  }
  catch(Exception ex)
  {
    Logger.WriteEnlistedExceptions(ex); // collect and write the exception
  }     
}

public void UpdateClients(clients)
{
  try
  {
     TransactionScope scope1 = new TransactionScope();
     foreach (client c in clients)
     {
       /*interanl*/ UpdateClient(c);
     }
     scope1.Complete();
  }
  catch (Exception ex)
  {
      Logger.WriteEnlistedExceptions(ex);           
  }
}

public partial class Logger
{
  private static List<Exception> _exceptionList = new List<Exception>();
  public static void EnlistException(Exception ex)
  {
    _exceptionList.Add(ex);
  }
  public static void WriteEnlistedExceptions()
  {
    foreach(Exception ex in _exceptionList)
      LogException(ex);
    _exceptionList.Clear();
  }
}
niio
  • 326
  • 3
  • 15