Thursday 17 November 2016

NServiceBus doesn't create TimeoutEntity or Subscription databases, even in Integration mode

Note: this refers to NServiceBus 4.6.5. Other versions may differ.

I had a problem whereby I was running an NServiceBus server on a new machine. It was using the
NHibernate ORM with SQL Server as the persistence store.

However, even with the endpoint running in NServiceBus.Integration mode, I found that the underlying databases were not being created. On starting the endpoint it would throw an ADOException with messages like

"could not execute query\r\n[ SELECT this_.Id as y0_, this_.Time as y1_ FROM TimeoutEntity this_ WHERE this_.Endpoint = @p0 and (this_.Time >= @p1 and this_.Time <= @p2) ORDER BY this_.Time asc ]\r\n  Name:cp0 - Value:SchemaCreator  Name:cp1 - Value:17/11/2006 15:10:37  Name:cp2 - Value:17/11/2016 15:10:37\r\n[SQL: SELECT this_.Id as y0_, this_.Time as y1_ FROM TimeoutEntity this_ WHERE this_.Endpoint = @p0 and (this_.Time >= @p1 and this_.Time <= @p2) ORDER BY this_.Time asc]"

as it was unable to read from the (not yet created) database.

The startup code was below:


        
        private static IBus SetupBus()
        {
            Configure.Serialization.Json();
            return Configure
                    .With(GetAssembliesToScan())
                    .DefaultBuilder()
                    .UseNHibernateSubscriptionPersister()
                    .UseNHibernateTimeoutPersister()
                    .UseNHibernateSagaPersister()
                    .UseNHibernateGatewayPersister()
                    .UseInMemoryGatewayDeduplication()
                    // Hack the unobtrusive conventions to force the console to work.
                    .DefiningEventsAs(t => t.Namespace != null && t.Namespace.StartsWith("Orders.Contracts"))
                    .UnicastBus()

                    // Load handlers to allow subscriptions to be set up.
                    .LoadMessageHandlers()
                    .CreateBus()
                    .Start(() => Configure.Instance.ForInstallationOn().Install());
        }

        private static Assembly[] GetAssembliesToScan()
        {
            return new[]
            {
                typeof(IOrder).Assembly,
                typeof(Program).Assembly
            };
        }

In the end after stepping through lots of NServiceBus and NServiceBus.NHibernate code I found the answer to the problem.
NServiceBus scans assemblies for code implementing the INeedToInstallSomething interface. The NServiceBus.Unicast.Subscriptions.NHibernate.Installer.Installer class implements this interface and it is this which calls the NHibernate SchemaUpdate.Execute() method.

However, despite the application's XML configuration referring to NHibernate and the code above explicitly defining the NHibernate persister, the installation code is not run because the implementing classes have not been scanned.

The solution is to modify the code GetAssembliesToScan method above to include the NServiceBus.NHibernate assembly.


        
        private static Assembly[] GetAssembliesToScan()
        {
            return new[]
            {
                typeof(IOrder).Assembly,
                typeof(Program).Assembly,
                typeof(NServiceBus.Unicast.Subscriptions.NHibernate.Installer.Installer).Assembly
            };
        }

Additionally, if your endpoint works with a Saga you need to scan the assembly that contains the SagaData.

public static IEnumerable AssembliesToScan
        {
            get
            {
                yield return typeof(IOrderCreated).Assembly;
                yield return typeof(OrderSagaData).Assembly;
                yield return typeof(NServiceBus.Unicast.Subscriptions.NHibernate.Installer.Installer).Assembly;
            }
        }

No comments:

Post a Comment