/// <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); } }