-
Notifications
You must be signed in to change notification settings - Fork 259
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
[Feature] Does Outbox supports multiple connections? #2104
Comments
@preardon would you have some ideas on how best to do this? |
It's specifically marked as private as it isn't considered as something we should be parsing in / a concern of your application. However this approach doesn't allow multiple dB contexts. Let me have a think about this requirement and get back to you |
@preardon, note that this limitation doesn't apply only on DbContexts / EF, but it applies also when using pure SQL connections/transactions. I write here a couple of options based on that overload which should be part of the IAmACommandProcessor interface. After the method will be public, we'll have many options to consume it: [HttpGet]
[Route("greeting")]
public async Task<HttpResponseMessage> TestPubCtx1Async(CancellationToken cancellationToken)
{
using (var trx1 = _greetingsCtx.Database.BeginTransaction())
{
var entity = new GreetingEntity() { GreetingText = "asdasd1" };
_greetingsCtx.Greetings.Add(entity);
_greetingsCtx.SaveChanges();
var integrEvent = new GreetingChangedEvent("Hello from the web1");
// Option1:
_commandProcessor.DepositPost(greeting, _context);
trx1.Commit();
}
return new HttpResponseMessage(System.Net.HttpStatusCode.NoContent);
}
static class CommandProcessor_EntityFrameworkCore_Extensions
{
public static Guid DepositPost<TRequest, TCont>(this IAmACommandProcessor processor, TRequest request, TCont context)
where TRequest : class, IRequest
where TCont : DbContext
{
var connectionProvider = new MsSqlEntityFrameworkCoreConnectionProvider<TCont>(context);
return processor.DepositPost(request, connectionProvider);
}
} Options2: uses a more generic TransactionConnectionProvider, which doesn't depend on EntityFramework, so we should be able to reuse it with Dapper as well with pure ADO net transactions... [HttpGet]
[Route("greeting")]
public async Task<HttpResponseMessage> TestPubCtx1Async(CancellationToken cancellationToken)
{
using (var trx1 = _greetingsCtx.Database.BeginTransaction())
{
var entity = new GreetingEntity() { GreetingText = "asdasd1" };
_greetingsCtx.Greetings.Add(entity);
_greetingsCtx.SaveChanges();
var integrEvent = new GreetingChangedEvent("Hello from the web1");
// Option2:
_commandProcessor.DepositPost(greeting, transaction.GetDbTransaction());
trx1.Commit();
}
return new HttpResponseMessage(System.Net.HttpStatusCode.NoContent);
}
static class CommandProcessor_DbTransaction_Extensions
{
public static Guid DepositPost<TRequest>(this IAmACommandProcessor processor, TRequest request, DbTransaction dbTransaction)
where TRequest : class, IRequest
{
var transactionProvider = new MsSqlTransactionConnectionProvider(dbTransaction);
return processor.DepositPost(request, transactionProvider);
}
}
public class MsSqlTransactionConnectionProvider : IMsSqlTransactionConnectionProvider
{
private readonly DbTransaction _dbTransaction;
public MsSqlTransactionConnectionProvider(DbTransaction dbTransaction)
{
_dbTransaction = dbTransaction;
}
public SqlConnection GetConnection()
{
return (SqlConnection) _dbTransaction.Connection;
}
public Task<SqlConnection> GetConnectionAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult((SqlConnection)_dbTransaction.Connection);
}
public SqlTransaction GetTransaction()
{
return (SqlTransaction) _dbTransaction;
}
public bool HasOpenTransaction => _dbTransaction != null;
public bool IsSharedConnection => true;
} |
So the Problem with option two is you have then tied the command processor to adodb Transaction. I was thinking of something more towards a Transaction provider that takes a "Name" or identifier where you can then you can specify a name/identifier if you want to pick one, this way it's generic enough that it will work for all connection types. |
Actually, this is the signature: For the option2 I didn't update the sample, but the idea is to create the connection/transaction using pure ADO net. |
If we inject the IAmABoxTransactionProvider this would work, however that is a Brighter internal and I feel it's better to set a Default and give the ability to Pass a name, this also means you don't have to rely on DI either |
Unless there are any particular drawbacks, imho, it would be perfect if we could have the option to choose between the two different approaches:
The explicit approach would give to Brighter more flexibility because we'll be able to adopt it in more complex and generic scenarios, like the one below, which involves an ADO transaction created explicitly and shared by many DbContexts. using (var conn = new SqlConnection(_dbConnString))
{
var greetingsCtx = new GreetingContext(conn);
var anotherCtx = new AnotherContext(conn);
conn.Open();
using (var trx1 = conn.BeginTransaction())
{
var entity1 = new GreetingEntity() { GreetingText = "asdasd1" };
greetingsCtx.Database.UseTransaction(trx1);
greetingsCtx.Greetings.Add(entity1);
greetingsCtx.SaveChanges();
var integrEvent1 = new GreetingChangedEvent("Hello from the web1");
_outbox.DepositPost(integrEvent1, trx1);
var entity2 = new TestEntity() { Text = "asdasd2" };
anotherCtx.Database.UseTransaction(trx1);
anotherCtx.SomeEntities.Add(entity2);
anotherCtx.SaveChanges();
var integrEvent2 = new TestChangedEvent("Hello from the web2");
_outbox.DepositPost(integrEvent2, trx1);
trx1.Commit(); // commits changes to _greetingsCtx + _anotherCtx + _outbox
}
} By looking at the code above, you can clearly see which are all the components involved in the same transaction, because each component is explicitly referencing the trx1. _greetingsCtx.Database.UseTransaction(trx1);
_outbox.DepositPost(integrEvent1, trx1);
_anotherCtx.Database.UseTransaction(trx1);
_outbox.DepositPost(integrEvent2, trx1); Do you see any drawback in providing this additional option? |
@IlSocio doing what you've stated above would close this to other providers as well as bring along additional dependencies, I will knock out a quick Draft PR of something that I think would be a little cleaner and submit it in an hour or so when I've got it working |
@preardon We should revisit this in the context of V10 |
Hello,
I'm trying to use the Outbox implementation of Brighter, but it is not clear to use the Outbox when we have multiple SQL connections.
eg.
In the IOC I'm registering the ConnectionProvider using
.UseMsSqlTransactionConnectionProvider()
Which will point to only 1 of the 2 possible Contexts.
Ideally, there exists this overload, which would allow me to explicitly provide the transaction, but, for some reason, it is marked as "private"
Brighter/src/Paramore.Brighter/CommandProcessor.cs
Line 455 in 7ad1a91
Would you accept a pull-request to make it available as Public, so that I use the Outbox on multiple connections?
Or there exists other way to archive the same result?
The text was updated successfully, but these errors were encountered: