0

There is such a code in which the first method can throw an exception (for example, if the connection to the database fails). But, in this case, we absolutely need to get and return the result of the second (Method 2) asynchronous method. If everything is fine with the first method and there is no exception, then we return the data from the first method. I hope I explained it clearly :) So, how to do it wisely?)

public async Task<IEnumerable<User>> GetUsersAsync(int departmentId,
    CancellationToken token)
{
    try
    {
        // Method 1: An exception can be thrown here
        var usersFromSqlDb = await _userSqlRepository
            .GetUsersAsync(departmentId, token); 

        if (!usersFromSqlDb.Any())
        {
            // Method 2
            var usersFromMongoDb = await _userMongoRepository
                .GetUsersAsync(departmentId, token);
            return usersFromMongoDb;
        }

        return usersFromSqlDb;
    }
    catch (CannotAnyUserException)
    {
        throw;
    }
    catch (Exception ex)
    {
        throw new CannotAnyUserException();
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Yuriy
  • 163
  • 1
  • 10
  • 1
    the second method should be in the catch block so it only runs if the first method throws an error – big boy Nov 23 '22 at 15:21
  • 2
    If you're sure `Method2` never throws an exception, you can put in the `finally` block. – Poul Bak Nov 23 '22 at 15:24
  • Exceptions work the same way, whether you call an asynchronous method or not. Once an exception is thrown, execution jumps to the `catch` block and then `finally`. If you want the second call to run even if the first fails it should be in the `finally` block or outside the `try/catch` entirely. If you want to handle exceptions thrown from the second method it should have its own `try/catch` – Panagiotis Kanavos Nov 23 '22 at 15:30
  • I don't understand: If `Method1` throws an exception, isn't it likely that `Method2` will throw the same exception? – Poul Bak Nov 23 '22 at 15:31
  • @PoulBak from the names I'd guess one connects to a SQL Server database and the other to MongoDB. If anything, it's the second method that's more likely to throw – Panagiotis Kanavos Nov 23 '22 at 15:32
  • @PanagiotisKanavos: You might be right - unless it's a network problem. – Poul Bak Nov 23 '22 at 15:34
  • As a side note, the result type `Task>` is a red flag. A `Task` represents something that will complete in the future, and an `IEnumerable` represents a sequence that is produced with [deferred execution](https://stackoverflow.com/questions/1168944/how-to-tell-if-an-ienumerablet-is-subject-to-deferred-execution). Future+deferred is probably something more complex than what you want it to be. I would suggest to change the result type to a `Task>` or a `Task` (a `Task` of a materialized collection). – Theodor Zoulias Nov 23 '22 at 15:45

1 Answers1

1

An idea is to have two return points. One for the case that the Method 1 succeeds and has a non-empty result, and a second one for all other cases:

public async Task<IEnumerable<User>> GetUsersAsync(int departmentId,
    CancellationToken token)
{
    try
    {
        // Method 1: An exception can be thrown here
        var usersFromSqlDb = await _userSqlRepository
            .GetUsersAsync(departmentId, token); 
        if (usersFromSqlDb.Any()) return usersFromSqlDb;
    }
    catch { } // Suppress the exception

    // Method 2
    var usersFromMongoDb = await _userMongoRepository
        .GetUsersAsync(departmentId, token);
    return usersFromMongoDb;
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104