Search code examples
c#visual-studio.net-assembly

Alternative to Conditional Compilation in C# for Database Initialisation


I'm investigating an alternative implementation for database initialisation that doesn't rely upon #if #elif..... (doens't work for .Net code in VS2017 known issue) to select customer specific database initialisers. One approach which seems sensible for both development and CI/CD environments is the following:

  • Create a database initialiser interface.
  • Create a c# assembly project for each site that contains an instance of the above interface.
  • Configure the database layer of the solution to load the database initialiser assembly at runtime.
  • Write a powershell script with string parameter 'siteName', builds the solution (based on siteName parameter) renames the site specific database initialiser so that the above step can load the assembly at runtime.

I'm interested to gauge peoples option of this approach to me it seems a scalable solution to the problem, my only concern is loading the assembly at runtime but I guess strong naming and code signing the assembly will mitigate the risk.

This is the type of DI init I want to change remove dependency on #if....

private void DatabaseInitialiseDefinitions(ContainerBuilder builder)
{
        #if site1
                    builder.RegisterType<site1ResourceIdentityDefinitions>()
                        .As<IResourceIdentityDefinition>()
                        .SingleInstance();
                    builder.RegisterType<site1UserDefinitions>()
                        .As<IUserDefinitions>()
                    .SingleInstance();
        #elif site2
                    builder.RegisterType<site2ResourceIdentityDefinition>()
                        .As<IResourceIdentityDefinition>()
                        .SingleInstance();
                    builder.RegisterType<site2UserDefinitions>()
                        .As<IUserDefinitions>()
                        .SingleInstance();
        #elif site3
                    builder.RegisterType<site3ResourceIdentityDefinition>()
                        .As<IResourceIdentityDefinition>()
                        .SingleInstance();
                    builder.RegisterType<site3UserDefinitions>()
                        .As<IUserDefinitions>()
                        .SingleInstance();
}

Solution

  • If it was just one place that you need to do this - then factory pattern (see the example of replacing compiler directives) to return tenant implementation or something similar is an option.

    If it is more tenant-specific versions doing it with DI directly is a lot simpler than build time. Even if you just optionally register additional tenant implementations a DI container like Autofac will pull out the last matching registration unless you ask for an IEnumerable of services. If it was me I would use Dependency Injection and treat the app as multi-tenant. It may be that each app runs in isolation but the code is still multi-tenant.

    If you had lots of registrations I'd use child containers to hold registrations that are specific to each tenant.

    If you used Autofac then this extension would work - https://github.com/autofac/Autofac.AspNetCore.Multitenant or https://github.com/autofac/Autofac.Multitenant (if not .Net Core)

    We have used it with multiple strategies to identify the tenant - simplest was simply a configuration setting in app.config that told it which tenant it was running as. The current one is based on hostname so all tenants run under one app. If it was a local application I'd expect a setting in a config that told it which tenant to be.