0

I want to return a Func middle of a using block. Should I worry about disposing of, before the user runs the result Func?

A sample code:

private IDbContextTransaction _transaction;

public Func<Task> BeginTransaction()
{
    Task lockDispose = CommitTransaction();
    using (_transaction = _dbContext.Database.BeginTransaction())
    {
        return async() =>
        {
            await lockDispose;
        };
        Task.WaitAll(lockDispose); //This code is unreachable.
    }
}

private async Task CommitTransaction()
{
    _transaction.Commit();
    await Task.CompletedTask;
}

Note that the execution time of the result Func is up to the user of this service.

I checked This Question and it's not my answer.

Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26
FarhadGh
  • 134
  • 11
  • 1
    When the func is returned, the transaction will have been disposed while the task inside of the func will already be running. That's probably not what you want, judging from your method names? You need to specify more precisely what you are trying to achieve. – Mo B. Dec 20 '20 at 07:05
  • I want delay dispose until user decides to execute the result func – FarhadGh Dec 20 '20 at 07:09
  • Could you include an example of how you intend to use the `BeginTransaction` method? – Theodor Zoulias Dec 20 '20 at 09:50
  • I wanted to achieve this goals: 1. No one can commit the transaction twice (So I hided CommitTransaction) 2. Do not force the user use using block (or single line using) 3. User can do anything with dbContext and multiple saveChanges before committing the transaction __ Also default usage for my case is in a middleware, before and after 'await next(context);' – FarhadGh Dec 20 '20 at 13:48
  • But I couldn't achieve these goals. I returned the Transaction from service, unfortunately. – FarhadGh Dec 20 '20 at 13:54
  • I don't think that giving to the user `Func` return values is meaningful or convenient. If you don't want them to commit the transaction twice, you could give them a `Transaction` facade with a `CommitTransaction` method that invokes the real underlying `Transaction.CommitTransaction` at most once. – Theodor Zoulias Dec 21 '20 at 03:52

1 Answers1

0

Since you want your user to be able to work with the connection between calling BeginTransaction and CommitTransaction, then the only thing you can do is to move disposal outside of this scope:

private IDbContextTransaction _transaction;

public Func<Task> BeginTransaction()
{
    _transaction = _dbContext.Database.BeginTransaction()
    return CommitTransaction;
}

private async Task CommitTransaction()
{
    _transaction.Commit();
    _transaction.Dispose();
    await Task.CompletedTask;
}

// I advice you implement IDisposable fully, but 
// this will do for the sake of demonstration
public void Dispose()
{
    _transaction?.Dispose();
}

And then somewhere outside you'll need to do something along the lines of:

using (var obj = TheWayYouInitializeYourObject())
{
    var commit = obj.BeginTransaction();
    // DO WORK
    await commit();
}

or

try
{
    var commit = obj.BeginTransaction();
    // DO WORK
    await commit();
}
finally
{
    obj.Dispose();
}

Basically, your using cannot span the gap in which your user works, so they'll have to do it on their own.

It is generally a good idea to make objects that have to work with disposables disposable as well.

user4182984
  • 222
  • 2
  • 9
  • I doubt that after calling BeginTransaction(), the transaction begins. And user must work with dbContext after that. I will test your solution and will come again to update this comment. – FarhadGh Dec 20 '20 at 07:21
  • @FarhadGh Updated the answer. – user4182984 Dec 20 '20 at 07:36