Outbox, EF and using DbContext outside of MessageHandler

In January I asked how to implement Outbox with EF DbContext and was guided to a working solution. But Particular unfortunately never responded when asked how to do it gracefully.

Now I have this OutboxDbContextFactory and want to remove NullReferenceException

It should be possible with a Behavior implementation.

Is it an option to do a simple GetService<ISqlStorageSession>() instead of GetRequiredService<ISqlStorageSession>()? This first one just returns null when the service is not available, so you can check on that and only create and return the new dbcontext when that is the case.

No unfortunately, the service provider calls a factory that makes this NullReferenceException anyway. The NullReferenceException is not in this code.

@Stig_Christensen

I am not sure why this hasn’t been picked up earlier by us, on behalf of the team, my sincere apologies. I’ll do my best to catch up here.

At the time of starting to support first class dependency injection for the persistence-specific session objects, we have decided to use the public extension methods within the DI resolution to downcast the core specific type to the persistence specific one. The idea was that with this in place, users would get a “meaningful” exception when the persistence-specific type is not available. So much about the idea :wink:

Unfortunately, SQL Persistence has a guard class that does a null check before it does the actual does the type implement the persistence-specific interface check which prevents that meaningful exception from happening which then blows in your face with a NullReferenceException. At this stage though, changing how all the persisters behave across the board might be quite a bit of an undertaking, especially given this scenario that you want to achieve has a good enough workaround (happy to be convinced otherwise, please let me know your thoughts).

Here is the workaround

instead of writing

var storage = _serviceProvider.GetRequiredService<ISqlStorageSession>();
...

For NServiceBus v7.8 change it to

var storage = _serviceProvider.GetService<SynchronizedStorageSession>();
if (storage != null)
{
   var session = storage.SqlPersistenceSession();
   context.Database.SetDbConnection(session.Connection);
   context.Database.UseTransaction(session.Transaction);
}
return context;

For NServiceBus 8.x change it to

if (_serviceProvider.GetService<ISynchronizedStorageSession>() is ISqlStorageSession { Connection: not null } storage)
{
   context.Database.SetDbConnection(storage.Connection);
   context.Database.UseTransaction(storage.Transaction);
}
return context;

This code would also work should you wish to start using the transactional session in your code as illustrated in the Using TransactionalSession with Entity Framework and ASP.NET Core sample.

Hope that helps

regards
Daniel

Thanks. I am using 7.8.1 and SynchronizedStorageSession is just an empty Interface!?

For some reason, I forgot to include the proper extension method usage. I updated the snippet in the above comment. The gist is

var storage = _serviceProvider.GetService<SynchronizedStorageSession>();
if (storage != null)
{
   var session = storage.SqlPersistenceSession();
   context.Database.SetDbConnection(session.Connection);
   context.Database.UseTransaction(session.Transaction);
}
return context;

Please let me know if that helps

Daniel

Thanks. Almost there. It compiles, but no I get a

System.InvalidOperationException: 'Cannot resolve scoped service ‘NServiceBus.Persistence.SynchronizedStorageSession’ from root

… when resolving my DbContext inside a scope (but not in a messagehandler)

My implementations is build on hints from @DorianGreen so maybe I did something wrong.

Shouldn’t Particular provide a working example on github on this?

Hi @Stig_Christensen

It seems the builder you get into that factory of yours is the root one and not the scoped one. This doesn’t work. The synchronized storage session is a scoped dependency which has to be resolved on the scoped builder and cannot be resolved on the root.

We have samples that show how to integrate SqlPersistence with Entity Framework and EntityFramework Core

Those samples show how you can connect entity framework contexts resolved within a message handler to the Sql Persistence managed connections and transactions.

With the introduction of the transactional session it becomes possible to have the same connection mechanisms also work “outside the message handler” which was previously not possible. This is when we added the sample that shows how you can connect these things together

The DI connecting tissue mentioned there and also highlighted here will work in both cases when the transactional session is available or the message handler is executed. You have a third case that you are trying to manage in your “infrastructure” that is concerned with “no transactional session” and “no message handler pipeline” available, which is currently not directly covered in our samples that is correct. It is indirectly covered though in the scenario highlighted in the transactional session in the else part of the snippet.

Unfortunately, it is difficult for us to provide samples for all possible usage scenarios that might occur in the wild. Happy to help you out resolving your current struggles and then see what we can do to potentially extract more guidance or improve existing ones.

You can also raise a ticket to us, and we can help you troubleshoot your code directly, in case you cannot share more details here due to privacy concerns.

Regards,

Daniel

Thanks. My DbContextFactory was added as a singleton, should have been scoped. Now it works

1 Like