/// <summary>
 /// <para>
 /// Registers an implementation based on Standard SQL, which should work with most SQL implementations.
 /// Use the options to specify the database connection.
 /// </para>
 /// <para>
 /// A vendor-specific implementation is preferred over this.
 /// Unlike this one, such an implementation may be able to create the table if it does not exist, and it may be optimized for that specific database.
 /// </para>
 /// <para>
 /// This overload takes a custom factory of <see cref="DbConnection"/> objects.
 /// </para>
 /// <para>
 /// The implementation will throw if the database does not exist, or if the table does not exist.
 /// </para>
 /// </summary>
 /// <param name="connectionFactory">A function that provides new DbConnection objects.</param>
 /// <param name="connectionString">Written onto produced <see cref="DbConnection"/> objects, if given. Required only if a <see cref="DbConnection"/> is produced without a connection string.</param>
 /// <param name="databaseName">If the connection factory's connection string does not specify the database name, specify it here instead.</param>
 public static ApplicationInstanceIdSourceExtensions.Options UseStandardSql(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                            Func <DbConnection> connectionFactory, string?connectionString = null,
                                                                            string?databaseName = null)
 {
     // Piggyback on the other overload, using a meaningless dependency that is always available
     return(UseStandardSql <IHostApplicationLifetime>(options, _ => connectionFactory(), connectionString, databaseName));
 }
        /// <summary>
        /// Registers a fixed-value implementation, using the given value factory.
        /// </summary>
        public static ApplicationInstanceIdSourceExtensions.Options UseFixedSource(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                   Func <IServiceProvider, ushort> applicationInstanceIdFactory)
        {
            options.Services.AddSingleton <IApplicationInstanceIdRenter, UnusedApplicationInstanceIdRenter>();            // Register an unused renter to satisfy the base registration
            options.Services.AddSingleton(CreateInstance);

            return(options);

            // Local function that returns a new instance
            IApplicationInstanceIdSource CreateInstance(IServiceProvider serviceProvider)
            {
                ushort applicationInstanceId;

                // Use a scope, in case scoped services are used to resolve the value
                // Then resolve the value once, and store it in our singleton instance
                using (var scope = serviceProvider.CreateScope())
                    applicationInstanceId = applicationInstanceIdFactory(scope.ServiceProvider);

                var instance = new FixedApplicationInstanceIdSource(
                    serviceProvider.GetRequiredService <IHostEnvironment>(),
                    applicationInstanceId);

                return(instance);
            }
        }
 /// <summary>
 /// Registers a fixed-value implementation that provides the given value, as IApplicationInstanceIdSource and as FixedApplicationInstanceIdSource.
 /// </summary>
 public static ApplicationInstanceIdSourceExtensions.Options UseFixedSource(this ApplicationInstanceIdSourceExtensions.Options options, ushort applicationInstanceId)
 {
     return(UseFixedSource(options, serviceProvider => applicationInstanceId));
 }
        /// <summary>
        /// <para>
        /// Registers an implementation based on Standard SQL, which should work with most SQL implementations.
        /// Use the options to specify the database connection.
        /// </para>
        /// <para>
        /// A vendor-specific implementation is preferred over this.
        /// Unlike this one, such an implementation may be able to create the table if it does not exist, and it may be optimized for that specific database.
        /// </para>
        /// <para>
        /// This overload takes a generic dependency to inject, and a function to get a <see cref="DbConnection"/> from that dependency.
        /// The dependency must be registered separately.
        /// </para>
        /// <para>
        /// The type parameter determines the database connection factory to get from the service provider, which should be registered separately.
        /// </para>
        /// <para>
        /// The implementation will throw if the database does not exist, or if the table does not exist.
        /// </para>
        /// </summary>
        /// <param name="getConnectionFromFactory">A function that gets a new <see cref="DbConnection"/> from the registered connection factory.</param>
        /// <param name="connectionString">Written onto produced <see cref="DbConnection"/> objects, if given. Required only if a <see cref="DbConnection"/> is produced without a connection string.</param>
        /// <param name="databaseName">If the connection factory's connection string does not specify the database name, specify it here instead.</param>
        public static ApplicationInstanceIdSourceExtensions.Options UseStandardSql <TDatabaseConnectionFactory>(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                                Func <TDatabaseConnectionFactory, DbConnection> getConnectionFromFactory, string?connectionString = null,
                                                                                                                string?databaseName = null)
            where TDatabaseConnectionFactory : class
        {
            // Register an IDbConnectionFactory
            ApplicationInstanceIdSourceDbConnectionFactory.Register(options.Services, getConnectionFromFactory, connectionString);

            // Register an IApplicationInstanceIdSourceTransactionalExecutor that uses the IDbConnectionFactory
            options.Services.AddTransient <IApplicationInstanceIdSourceTransactionalExecutor, SqlTransactionalExecutor>();

            // Register the IApplicationInstanceIdRenter that uses all of the above
            options.Services.AddTransient(CreateInstance);

            return(options);

            // Local function that creates a new instance
            IApplicationInstanceIdRenter CreateInstance(IServiceProvider serviceProvider)
            {
                var instance = new StandardSqlApplicationInstanceIdRenter(serviceProvider, databaseName);

                return(instance);
            }
        }
        /// <summary>
        /// <para>
        /// Registers an implementation based on SQLite. Use the options to specify the database connection.
        /// </para>
        /// <para>
        /// This overload takes a generic dependency to inject, and a function to get a <see cref="DbConnection"/> from that dependency.
        /// The dependency must be registered separately.
        /// </para>
        /// <para>
        /// The type parameter determines the database connection factory to get from the service provider, which should be registered separately.
        /// </para>
        /// <para>
        /// The implementation will throw if the database does not exist.
        /// The table, however, is created automatically, because different instances of the table may be used in various databases or bounded contexts.
        /// </para>
        /// <para>
        /// To use an in-memory SQLite database, supply a factory that returns an <strong>open</strong> connection, and that always returns the same connection instance.
        /// </para>
        /// </summary>
        /// <param name="getConnectionFromFactory">A function that gets a new <see cref="DbConnection"/> from the registered connection factory.</param>
        /// <param name="connectionString">Written onto produced <see cref="DbConnection"/> objects, if given. Required only if a <see cref="DbConnection"/> is produced without a connection string.</param>
        /// <param name="databaseName">If the connection factory's connection string does not specify the database name, specify it here instead.</param>
        public static ApplicationInstanceIdSourceExtensions.Options UseSqlite <TDatabaseConnectionFactory>(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                           Func <TDatabaseConnectionFactory, DbConnection> getConnectionFromFactory, string?connectionString = null,
                                                                                                           string?databaseName = null)
            where TDatabaseConnectionFactory : class
        {
            var firstConnectionIsResolvedSuccessfully = false;

            // Register an IDbConnectionFactory
            ApplicationInstanceIdSourceDbConnectionFactory.Register(options.Services, (Func <TDatabaseConnectionFactory, DbConnection>)CreateDbConnection, connectionString);

            // Register an IApplicationInstanceIdSourceTransactionalExecutor that uses the IDbConnectionFactory
            options.Services.AddTransient <IApplicationInstanceIdSourceTransactionalExecutor, SqlTransactionalExecutor>();

            // Register the IApplicationInstanceIdRenter that uses all of the above
            options.Services.AddTransient(CreateInstance);

            return(options);

            // Local function that creates a new instance
            IApplicationInstanceIdRenter CreateInstance(IServiceProvider serviceProvider)
            {
                var instance = new SqliteApplicationInstanceIdRenter(serviceProvider, databaseName);

                return(instance);
            }

            // Local function that creates a DbConnection from the factory
            DbConnection CreateDbConnection(TDatabaseConnectionFactory factory)
            {
                var connection = getConnectionFromFactory(factory);

                if (!firstConnectionIsResolvedSuccessfully)
                {
                    if (connection.ConnectionString.Contains(":memory:", StringComparison.OrdinalIgnoreCase))
                    {
                        if (connection.State != ConnectionState.Open)
                        {
                            throw new Exception($"To use an in-memory SQLite database, configure a fixed and preopened connection.");
                        }

                        // Not perfect since we cannot use a scope, but we verify what we can
                        var secondConnection = getConnectionFromFactory(factory);
                        if (!ReferenceEquals(connection, secondConnection))
                        {
                            throw new Exception($"To use an in-memory SQLite database, configure a fixed and preopened connection.");
                        }
                    }
                    firstConnectionIsResolvedSuccessfully = true;
                }

                return(connection);
            }
        }
        /// <summary>
        /// <para>
        /// Registers an implementation based on SQLite, using a registered IDbContextFactory or DbContext.
        /// The use of AddPooledDbContextFactory or AddDbContextFactory is strongly recommended.
        /// </para>
        /// <para>
        /// This overload makes use of the registered DbContext, including all of its configuration, such as auto-retrying execution strategies.
        /// </para>
        /// <para>
        /// The implementation will throw if the database does not exist.
        /// The table, however, is created automatically, because different instances of the table may be used in various databases or bounded contexts.
        /// </para>
        /// </summary>
        /// <param name="databaseName">If the connection factory's connection string does not specify the database name, specify it here instead.</param>
        public static ApplicationInstanceIdSourceExtensions.Options UseSqliteDbContext <TDbContext>(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                    string?databaseName = null)
            where TDbContext : DbContext
        {
            options.UseSqlite(() => new DummyDbConnection(), connectionString: null, databaseName);

            AddEntityFrameworkTableName(options.Services);
            AddDbContextTransactionalExecutor <TDbContext>(options.Services, isSqlite: true);

            return(options);
        }
        /// <summary>
        /// <para>
        /// Registers an implementation based on a dedicated Azure Blob Storage Container. Use the options to specify the container.
        /// </para>
        /// <para>
        /// The storage container must be used for this purpose only, and no other content must ever exist in it.
        /// </para>
        /// <para>
        /// The implementation will throw if the Azure Storage Account is unreachable when the application instance ID is registered.
        /// The container, however, is created automatically if it does not exist.
        /// </para>
        /// </summary>
        public static ApplicationInstanceIdSourceExtensions.Options UseAzureBlobStorageContainer(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                 Func <IServiceProvider, BlobContainerClient> blobContainerClientFactory)
        {
            if (blobContainerClientFactory is null)
            {
                throw new ArgumentNullException(nameof(blobContainerClientFactory));
            }

            options.Services.AddTransient(CreateInstance);

            return(options);

            // Local function used to create an instance
            IApplicationInstanceIdRenter CreateInstance(IServiceProvider serviceProvider)
            {
                var blobContainerClient = blobContainerClientFactory(serviceProvider) ?? throw new Exception($"The factory produced a null {nameof(BlobContainerClient)}.");
                var blobContainerRepo   = new AzureBlobApplicationInstanceIdRenter.BlobContainerRepo(blobContainerClient);

                var instance = new AzureBlobApplicationInstanceIdRenter(serviceProvider, blobContainerRepo);

                return(instance);
            }
        }
        /// <summary>
        /// <para>
        /// Registers an implementation based on a dedicated Azure Blob Storage Container. Use the options to specify the container.
        /// </para>
        /// <para>
        /// The storage container must be used for this purpose only, and no other content must ever exist in it.
        /// </para>
        /// <para>
        /// The implementation will throw if the Azure Storage Account is unreachable when the application instance ID is registered.
        /// The container, however, is created automatically if it does not exist.
        /// </para>
        /// </summary>
        public static ApplicationInstanceIdSourceExtensions.Options UseAzureBlobStorageContainer(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                 BlobContainerClient blobContainerClient)
        {
            if (blobContainerClient is null)
            {
                throw new ArgumentNullException(nameof(blobContainerClient));
            }

            return(UseAzureBlobStorageContainer(options, serviceProvider => blobContainerClient));
        }
        /// <summary>
        /// <para>
        /// Registers an implementation based on SQL Server / Azure SQL. Use the options to specify the database connection.
        /// </para>
        /// <para>
        /// This overload takes a generic dependency to inject, and a function to get a <see cref="DbConnection"/> from that dependency.
        /// The dependency must be registered separately.
        /// </para>
        /// <para>
        /// The type parameter determines the database connection factory to get from the service provider, which should be registered separately.
        /// </para>
        /// <para>
        /// The implementation will throw if the database does not exist.
        /// The table, however, is created automatically, because different instances of the table may be used in various databases or bounded contexts.
        /// </para>
        /// </summary>
        /// <param name="getConnectionFromFactory">A function that gets a new <see cref="DbConnection"/> from the registered connection factory.</param>
        /// <param name="connectionString">Written onto produced <see cref="DbConnection"/> objects, if given. Required only if a <see cref="DbConnection"/> is produced without a connection string.</param>
        /// <param name="databaseAndSchemaName">If the connection factory's connection string does not specify the database and schema name, specify database.schema here.</param>
        public static ApplicationInstanceIdSourceExtensions.Options UseSqlServer <TDatabaseConnectionFactory>(this ApplicationInstanceIdSourceExtensions.Options options,
                                                                                                              Func <TDatabaseConnectionFactory, DbConnection> getConnectionFromFactory, string?connectionString = null,
                                                                                                              string?databaseAndSchemaName = null)
            where TDatabaseConnectionFactory : class
        {
            // Register a custom table name to use
            options.Services.AddSingleton(new ApplicationInstanceIdCustomTableName(SqlServerApplicationInstanceIdRenter.DefaultTableName));

            // Register an IDbConnectionFactory
            ApplicationInstanceIdSourceDbConnectionFactory.Register(options.Services, getConnectionFromFactory, connectionString);

            // Register an IApplicationInstanceIdSourceTransactionalExecutor that uses the IDbConnectionFactory
            options.Services.AddTransient <IApplicationInstanceIdSourceTransactionalExecutor, SqlTransactionalExecutor>();

            // Register the IApplicationInstanceIdRenter that uses all of the above
            options.Services.AddTransient(CreateInstance);

            return(options);

            // Local function that creates a new instance
            IApplicationInstanceIdRenter CreateInstance(IServiceProvider serviceProvider)
            {
                var instance = new SqlServerApplicationInstanceIdRenter(serviceProvider, databaseAndSchemaName);

                return(instance);
            }
        }