Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outbox implementation with TransactionScope #99

Open
JornWildt opened this issue Mar 26, 2023 · 2 comments
Open

Outbox implementation with TransactionScope #99

JornWildt opened this issue Mar 26, 2023 · 2 comments

Comments

@JornWildt
Copy link

The current Outbox implementation requires both an SqlConnection as well as an SqlTransaction as shown on https://github.com/rebus-org/Rebus/wiki/Outbox :

using var connection = GetSqlConnection();
using var transaction = connection.BeginTransaction();
using var scope = new RebusTransactionScope();

// and then enable the outbox for that scope
scope.UseOutbox(connection, transaction);

Unfortunately I work with a framework that uses TransactionScope - and I have found no way to get the SqlTransaction out of that.

Is there any way to get SqlTransaction out of SqlConnection (I haven't found one)?

Would it be possible to expand the Outbox implementation to work with TransactionScope in addition to SqlTransaction?

@mookid8000
Copy link
Member

Hmm curious as you made me I went through QuickWatch reflection on SqlConnection, and as you can see there's a SqlTransaction hiding in there, which can be retrieved through some private properties:
image

I then coded this little example to see if I could actually get it to work. The central bits look somewhat like this:

static SqlTransaction GetSqlTransactionViaReflection(SqlConnection connection)
{
    const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;

    var innerConnectionField = connection.GetType().GetProperty("InnerConnection", flags);
    var innerConnection = innerConnectionField?.GetValue(connection);

    var availableInnerTransactionField = innerConnection?.GetType().GetProperty("AvailableInternalTransaction", flags);
    var availableInnerTransaction = availableInnerTransactionField?.GetValue(innerConnection);

    var parentTransactionField = availableInnerTransaction?.GetType().GetProperty("Parent", flags);
    var parentTransaction = parentTransactionField?.GetValue(availableInnerTransaction) as SqlTransaction;

    return parentTransaction;
}

It succeeds in retrieving the SqlTransaction when it's an ordinary, manually started transaction, but it fails when the transaction is an ambient one started by the TransactionScope.

I have not been able to figure out a way to retrieve the transaction in any other way.

Maybe it would be a nice addition to the API to enable doing something like this:

// somewhere, possibly far out in the call hierarchy
using var txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);

// further down:
using var scope = new RebusTransactionScope();

scope.UseOutboxWithAmbientTransaction();

// whee!

I don't think it would be too hard to enable this, but it's hard to tell if there's a surprise or two lurking in there.

@JornWildt
Copy link
Author

Thanks for your work! scope.UseOutboxWithAmbientTransaction(); seems to fit my exact use case where my framework uses TransactionScopeall over the place. Should you ever find time to add it, then please ping me here :-)

BTW it is Cofoundry: https://www.cofoundry.org/docs/framework/data-access/transactions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants