public RavenDbSetupHostedService(RavenDbSetupOptions setupOptions, IServiceProvider serviceProvider, ILogger <RavenDbSetupHostedService> logger) { _setupOptions = setupOptions ?? throw new ArgumentNullException(nameof(setupOptions)); _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); }
/// <summary> /// Initializes a RavenDb database with some simple default settings. /// </summary> /// <remarks>The RavenDb instance is also setup as a Singleton in the IoC framework.<br/><b> No data is setup here. Use the SetupRavenDb extension method on an IDocumentStore, to do that.</b></remarks> /// <param name="services">Collection of services to setup.</param> /// <param name="options">Options required to initialize the database.</param> /// <param name="setupOptions">Optional: RavenDb setup options, like seeding data.</param> /// <returns>The same collection of services.</returns> public static IServiceCollection AddSimpleRavenDb(this IServiceCollection services, RavenDbOptions options, RavenDbSetupOptions setupOptions = null) { if (services is null) { throw new ArgumentNullException(nameof(services)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } const string missingRavenDbConfigurationText = "Missing RavenDb configuration setting: "; if (options.ServerUrls?.Any() == false) { throw new Exception($"{missingRavenDbConfigurationText}{nameof(options.ServerUrls)}"); } if (string.IsNullOrWhiteSpace(options.DatabaseName)) { throw new Exception($"{missingRavenDbConfigurationText}{nameof(options.DatabaseName)}"); } var documentStore = new DocumentStore { Urls = options.ServerUrls.ToArray(), Database = options.DatabaseName }; if (!string.IsNullOrWhiteSpace(options.X509CertificateBase64)) { // REF: https://ayende.com/blog/186881-A/x509-certificates-vs-api-keys-in-ravendb documentStore.Certificate = new X509Certificate2(Convert.FromBase64String(options.X509CertificateBase64)); } documentStore.Initialize(); services.AddSingleton <IDocumentStore>(documentStore); // Do we wish to setup our DB? We should really do this BEFORE we accept web requests (if a web host) // or before the main app (console app) starts. if (setupOptions != null) { services.AddSingleton(setupOptions); services.AddHostedService <RavenDbSetupHostedService>(); } return(services); }
/// <summary> /// Setup a RavenDb database, ready to be used with some seeded, fake data (if provided) and any indexes (if provided). /// </summary> /// <param name="documentStore">DocumentStore to check.</param> /// <param name="setupOptions">Optional: any custom setup options, like data to seed.</param> /// <param name="logger">Logger.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <remarks>Default Polly policy is 15 checks, every 2 seconds (if none was provided).</remarks> public static async Task SetupRavenDbAsync(this IDocumentStore documentStore, RavenDbSetupOptions setupOptions, ILogger logger, CancellationToken cancellationToken) { if (documentStore == null) { throw new ArgumentNullException(nameof(documentStore)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } logger.LogDebug($" - Checking if the database '{documentStore.Database}' exists and if any data exists..."); DatabaseStatistics existingDatabaseStatistics = null; try { var checkRavenDbPolicy = setupOptions?.Policy ?? CheckRavenDbPolicyAsync(logger, cancellationToken); await checkRavenDbPolicy.ExecuteAsync(async cancellationToken => { existingDatabaseStatistics = await documentStore.Maintenance.SendAsync(new GetStatisticsOperation(), cancellationToken); }, cancellationToken); } catch (DatabaseDoesNotExistException) { existingDatabaseStatistics = null; // No statistics because there's no Db tenant. } catch (AuthorizationException) { // We failed to authenticate against the database. This _usually_ means that we // probably didn't have ADMIN rights against a db (so we can't do high level stuff) // but we could still do other stuff, like add fake data for seeding the DB. // SUMMARY: Db Tenant might exist, so lets assume that it does (safer bet). existingDatabaseStatistics = new DatabaseStatistics(); } if (cancellationToken.IsCancellationRequested) { logger.LogInformation("Cancelling setting up RavenDb database ..."); cancellationToken.ThrowIfCancellationRequested(); } try { await SetupRavenDbPolicyAsync(logger, cancellationToken).ExecuteAsync(async cancellationToken => { await SetupDatabaseTenantAsync(documentStore, existingDatabaseStatistics != null, logger, cancellationToken); if (cancellationToken.IsCancellationRequested) { logger.LogInformation("Cancelling setting up RavenDb database ..."); cancellationToken.ThrowIfCancellationRequested(); } var documentCount = existingDatabaseStatistics?.CountOfDocuments ?? 0; if (documentCount > 0) { logger.LogDebug($" - Skipping seeding fake data because database has {documentCount:N0} documents already in it."); } else { await SeedCollectionsOfFakeDataAsync(documentStore, setupOptions?.DocumentCollections, logger, cancellationToken); } if (cancellationToken.IsCancellationRequested) { logger.LogInformation("Cancelling setting up RavenDb database ..."); cancellationToken.ThrowIfCancellationRequested(); } await SetupIndexesAsync(documentStore, setupOptions?.IndexAssembly, logger, cancellationToken); }, cancellationToken); } catch (Exception exception) { logger.LogError("Failed to setup RavenDb and all retries failed. Unable to continue. Application is now terminating. Error: {exception}", exception); throw; } }