SQL Persistence scripts and Azure functions

Hello. I created an issue at https://github.com/Particular/NServiceBus.AzureFunctions.ServiceBus/issues/125 but maybe this place is better for discussing it.

The generated SQL Script isn’t found when running NServiceBus within Azure Functions using the 0.5.0 NServiceBus.AzureFunctions.ServiceBus packade (along side the 6.0.4 NServiceBus.Persistence.Sql).

The endpoint is looking in different locations based on how we start the function:

Different locations based on how we start the endpoint:

Visual Studio:

System.Private.CoreLib: Exception while executing function: Core. NServiceBus.Persistence.Sql: Expected 'C:\Program Files\Microsoft\Azure Functions Core Tools\NServiceBus.Persistence.Sql\MsSqlServer\Outbox_Create.sql' to exist. It is possible it was not deployed with the endpoint.

Rider 2020.3:

System.Private.CoreLib: Exception while executing function: Core. NServiceBus.Persistence.Sql: Expected 'C:\Users\username\AppData\Roaming\JetBrains\Rider2020.3\azure-functions-coretools\3.0.3233\NServiceBus.Persistence.Sql\MsSqlServer\Outbox_Create.sql' to exist. It is possible it was not deployed with the endpoint.

func start from the CLI:

System.Private.CoreLib: Exception while executing function: Core. NServiceBus.Persistence.Sql: Expected 'C:\Users\username\AppData\Local\AzureFunctionsTools\Releases\3.18.0\cli_x64\NServiceBus.Persistence.Sql\MsSqlServer\Outbox_Create.sql' to exist. It is possible it was not deployed with the endpoint.

Any ideas?

Hi Jens,

Unfortunately, you’re running into a side effect of how Azure Functions work, in that they are actually run by a precompiled exe that dynamically loads your assembly.

The SQL Persistence development time script execution relies on AppDomain.CurrentDomain.BaseDirectory to try and find the right directory, but that’s not correct when used in Azure Functions.

There is a value you could try to manually set to override the default folder location, but there is no configuration API exposed for it.

It does seem like there are some improvements that could be made to make these two things work better together.

Thank you Brandon, it worked to set the value manually. Here’s what we did in our Function Startup file:

string basePath = IsDevelopmentEnvironment() ?
	Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot") :
	$"{Environment.GetEnvironmentVariable("HOME")}\\site\\wwwroot";

And in our UseNServiceBus builder:

var configuration = new ServiceBusTriggeredEndpointConfiguration(AzureServiceBusTriggerFunction.EndpointName, "AzureServiceBus_ConnectionString");

var settings = configuration.AdvancedConfiguration.GetSettings();
settings.Set("SqlPersistence.ScriptDirectory", basePath);

And the IsDevelopmentEnvironment method:

private bool IsDevelopmentEnvironment()
{
	return "Development".Equals(Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT"), StringComparison.OrdinalIgnoreCase);
}

A bit hacky, but it seems to work for now at least.

Jens,

I believe you don’t need IsDevelopmentEnvironment() method and custom basePath logic. You could get the base from the Functions context directly in the following way:

builder.GetContext().ApplicationRootPath;

Where builder is IFunctionsHostBuilder.

Your UseNServiceBus method would look like this:

var configuration = new ServiceBusTriggeredEndpointConfiguration(
  AzureServiceBusTriggerFunction.EndpointName, "AzureServiceBus_ConnectionString");

var settings = configuration.AdvancedConfiguration.GetSettings();
settings.Set("SqlPersistence.ScriptDirectory", builder.GetContext().ApplicationRootPath);

This should work for any environment. Have a go at it and see if it’s working for you.

Thank you Sean, I can confirm that:

builder.GetContext().ApplicationRootPath;

worked fine for us!