From dacf1c23b37d8acf47ba6e2317223f245a56f0da Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Tue, 19 Nov 2019 16:45:41 +0000 Subject: [PATCH] Add in multi persistence support to shared --- .../EventStore/AggregateRepositoryManager.cs | 47 ++++++ .../EventStore/IAggregateRepositoryManager.cs | 23 +++ .../EventStore/IEventStoreContextManager.cs | 137 +++++++++++++++++ Shared.EventStore/Shared.EventStore.csproj | 1 + .../ConnectionStringConfiguration.cs | 59 ++++++++ .../ConnectionStringConfigurationContext.cs | 109 ++++++++++++++ .../ConnectionStringType.cs | 26 ++++ Shared/EntityFramework/DbContextFactory.cs | 78 ++++++++++ Shared/EntityFramework/IDbContextFactory.cs | 25 ++++ ...20191119150009_InitialDatabase.Designer.cs | 80 ++++++++++ .../20191119150009_InitialDatabase.cs | 64 ++++++++ ...StringConfigurationContextModelSnapshot.cs | 78 ++++++++++ ...ConnectionStringConfigurationRepository.cs | 140 ++++++++++++++++++ Shared/Repositories/ConnectionStringType.cs | 8 + ...ConnectionStringConfigurationRepository.cs | 44 ++++++ Shared/Shared.csproj | 3 + 16 files changed, 922 insertions(+) create mode 100644 Shared.EventStore/EventStore/AggregateRepositoryManager.cs create mode 100644 Shared.EventStore/EventStore/IAggregateRepositoryManager.cs create mode 100644 Shared.EventStore/EventStore/IEventStoreContextManager.cs create mode 100644 Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfiguration.cs create mode 100644 Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfigurationContext.cs create mode 100644 Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringType.cs create mode 100644 Shared/EntityFramework/DbContextFactory.cs create mode 100644 Shared/EntityFramework/IDbContextFactory.cs create mode 100644 Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.Designer.cs create mode 100644 Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.cs create mode 100644 Shared/EntityFramework/Migrations/ConnectionStringConfigurationContextModelSnapshot.cs create mode 100644 Shared/Repositories/ConnectionStringConfigurationRepository.cs create mode 100644 Shared/Repositories/ConnectionStringType.cs create mode 100644 Shared/Repositories/IConnectionStringConfigurationRepository.cs diff --git a/Shared.EventStore/EventStore/AggregateRepositoryManager.cs b/Shared.EventStore/EventStore/AggregateRepositoryManager.cs new file mode 100644 index 00000000..b4fa3115 --- /dev/null +++ b/Shared.EventStore/EventStore/AggregateRepositoryManager.cs @@ -0,0 +1,47 @@ +namespace Shared.EventStore.EventStore +{ + using System; + using DomainDrivenDesign.EventStore; + + public class AggregateRepositoryManager : IAggregateRepositoryManager + { + #region Fields + + /// + /// The event store context manager + /// + private readonly IEventStoreContextManager EventStoreContextManager; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The event store context manager. + public AggregateRepositoryManager(IEventStoreContextManager eventStoreContextManager) + { + this.EventStoreContextManager = eventStoreContextManager; + } + + #endregion + + #region Methods + + /// + /// Gets the aggregate repository. + /// + /// + /// The identifier. + /// + public IAggregateRepository GetAggregateRepository(Guid identifier) where T : Aggregate, new() + { + IEventStoreContext context = this.EventStoreContextManager.GetEventStoreContext(identifier.ToString()); + + return new AggregateRepository(context); + } + + #endregion + } +} \ No newline at end of file diff --git a/Shared.EventStore/EventStore/IAggregateRepositoryManager.cs b/Shared.EventStore/EventStore/IAggregateRepositoryManager.cs new file mode 100644 index 00000000..b3c3e071 --- /dev/null +++ b/Shared.EventStore/EventStore/IAggregateRepositoryManager.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shared.EventStore.EventStore +{ + using DomainDrivenDesign.EventStore; + + public interface IAggregateRepositoryManager + { + #region Methods + + /// + /// Gets the aggregate repository. + /// + /// + /// The identifier. + /// + IAggregateRepository GetAggregateRepository(Guid identifier) where T : Aggregate, new(); + + #endregion + } +} diff --git a/Shared.EventStore/EventStore/IEventStoreContextManager.cs b/Shared.EventStore/EventStore/IEventStoreContextManager.cs new file mode 100644 index 00000000..66858e67 --- /dev/null +++ b/Shared.EventStore/EventStore/IEventStoreContextManager.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shared.EventStore.EventStore +{ + using System.Diagnostics; + using System.Threading; + using DomainDrivenDesign.EventStore; + using Microsoft.Extensions.Logging; + using Shared.Repositories; + + public interface IEventStoreContextManager + { + IEventStoreContext GetEventStoreContext(String connectionIdentifier); + } + + public class EventStoreContextManager : IEventStoreContextManager + { + #region Fields + + /// + /// The event store context function + /// + private readonly Func EventStoreContextFunc; + + private readonly IConnectionStringConfigurationRepository ConnectionStringConfigurationRepository; + + /// + /// The event store contexts + /// + private readonly Dictionary EventStoreContexts; + + /// + /// The context + /// + private readonly IEventStoreContext Context; + + //TODO static? + /// + /// The padlock + /// + private readonly Object padlock = new Object(); + + /// + /// Occurs when [trace generated]. + /// + public event TraceHandler TraceGenerated; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The event store context function. + public EventStoreContextManager(Func eventStoreContextFunc, + IConnectionStringConfigurationRepository connectionStringConfigurationRepository) + { + this.EventStoreContexts = new Dictionary(); + this.EventStoreContextFunc = eventStoreContextFunc; + this.ConnectionStringConfigurationRepository = connectionStringConfigurationRepository; + } + + /// + /// Initializes a new instance of the class. + /// + /// The event store context. + public EventStoreContextManager(IEventStoreContext eventStoreContext) + { + this.Context = eventStoreContext; + } + + #endregion + + #region Methods + + public IEventStoreContext GetEventStoreContext(String connectionIdentifier) + { + if (this.Context != null) + { + return this.Context; + } + + this.WriteTrace($"No resolved context found, about to resolve one using connectionIdentifier {connectionIdentifier}"); + + if (this.EventStoreContexts.ContainsKey(connectionIdentifier)) + { + return this.EventStoreContexts[connectionIdentifier.ToString()]; + } + + this.WriteTrace($"Creating a new EventStoreContext for connectionIdentifier {connectionIdentifier}"); + + lock (this.padlock) + { + if (!this.EventStoreContexts.ContainsKey(connectionIdentifier)) + { + // This will need to now look up the ES Connection string from persistence + String connectionString = this.ConnectionStringConfigurationRepository.GetConnectionString(connectionIdentifier, ConnectionStringType.EventStore, CancellationToken.None).Result; + + //this.WriteTrace($"Connection String is {connectionString}"); + + //IEventStoreContext eventStoreContext = this.EventStoreContextFunc(connectionString); + + //this.EventStoreContexts.Add(connectionStringId, eventStoreContext); + } + + return this.EventStoreContexts[connectionIdentifier]; + } + } + + + /// + /// Writes the trace. + /// + /// The trace. + private void WriteTrace(String trace) + { + if (this.TraceGenerated != null) + { + this.TraceGenerated(trace, LogLevel.Information); + } + } + + private void GuardAgainstNoConnectionIdentifier(String connectionIdentifier) + { + //Check if the connectionStringIdentifier is present + if (String.IsNullOrEmpty(connectionIdentifier)) + { + throw new ArgumentException("Value cannot be empty.", nameof(connectionIdentifier)); + } + } + + #endregion + } +} diff --git a/Shared.EventStore/Shared.EventStore.csproj b/Shared.EventStore/Shared.EventStore.csproj index f2df427c..962a178a 100644 --- a/Shared.EventStore/Shared.EventStore.csproj +++ b/Shared.EventStore/Shared.EventStore.csproj @@ -12,6 +12,7 @@ + diff --git a/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfiguration.cs b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfiguration.cs new file mode 100644 index 00000000..7690a096 --- /dev/null +++ b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfiguration.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace Shared.EntityFramework.ConnectionStringConfiguration +{ + using System.ComponentModel.DataAnnotations.Schema; + + public class ConnectionStringConfiguration + { + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + [Key] + public Guid Id { get; set; } + + /// + /// Gets or sets the connection string identifier. + /// + /// + /// The connection string identifier. + /// + [Required] + [Column("externalIdentifier")] + public String ExternalIdentifier { get; set; } + + /// + /// Gets or sets the connection string type identifier. + /// + /// + /// The connection string type identifier. + /// + public Int32 ConnectionStringTypeId { get; set; } + + /// + /// Gets or sets the type of the connection string. + /// + /// + /// The type of the connection string. + /// + [ForeignKey(nameof(ConnectionStringTypeId))] + public virtual ConnectionStringType ConnectionStringType { get; set; } + + + /// + /// Gets or sets the connection string. + /// + /// + /// The connection string. + /// + [Required] + [Column("connectionString")] + public String ConnectionString { get; set; } + } +} diff --git a/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfigurationContext.cs b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfigurationContext.cs new file mode 100644 index 00000000..9d529f2f --- /dev/null +++ b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringConfigurationContext.cs @@ -0,0 +1,109 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shared.EntityFramework.ConnectionStringConfiguration +{ + public class ConnectionStringConfigurationContext : DbContext + { + #region Fields + + /// + /// The connection string + /// + private readonly String ConnectionString; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class using the connection string called ReadModelContext in the app.config file. + /// + public ConnectionStringConfigurationContext() + { + // Paramaterless constructor required for migrations. + } + + /// + /// Initializes a new instance of the class using the connection string passed in. + /// + /// The connection string. + public ConnectionStringConfigurationContext(String connectionString) + { + this.ConnectionString = connectionString; + } + + /// + /// Initializes a new instance of the class. + /// + /// The database context options. + public ConnectionStringConfigurationContext(DbContextOptions dbContextOptions) : base(dbContextOptions) + { + } + + #endregion + + /// + /// Gets or sets the connection string configuration. + /// + /// + /// The connection string configuration. + /// + public DbSet ConnectionStringConfiguration { get; set; } + + /// + /// Gets or sets the type of the connection string. + /// + /// + /// The type of the connection string. + /// + public DbSet ConnectionStringType { get; set; } + + /// + /// + /// Override this method to configure the database (and other options) to be used for this context. + /// This method is called for each instance of the context that is created. + /// + /// + /// In situations where an instance of may or may not have been passed + /// to the constructor, you can use to determine if + /// the options have already been set, and skip some or all of the logic in + /// . + /// + /// + /// A builder used to create or modify options for this context. Databases (and other extensions) + /// typically define extension methods on this object that allow you to configure the context. + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (!string.IsNullOrWhiteSpace(this.ConnectionString)) + { + optionsBuilder.UseSqlServer(this.ConnectionString); + } + + base.OnConfiguring(optionsBuilder); + } + + /// + /// Override this method to further configure the model that was discovered by convention from the entity types + /// exposed in properties on your derived context. The resulting model may be cached + /// and re-used for subsequent instances of your derived context. + /// + /// The builder being used to construct the model for this context. Databases (and other extensions) typically + /// define extension methods on this object that allow you to configure aspects of the model that are specific + /// to a given database. + /// + /// If a model is explicitly set on the options for this context (via ) + /// then this method will not be run. + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasIndex(c => new + { + c.ExternalIdentifier, + c.ConnectionStringTypeId + }).IsUnique(); + } + } +} diff --git a/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringType.cs b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringType.cs new file mode 100644 index 00000000..06a3d916 --- /dev/null +++ b/Shared/EntityFramework/ConnectionStringConfiguration/ConnectionStringType.cs @@ -0,0 +1,26 @@ +namespace Shared.EntityFramework.ConnectionStringConfiguration +{ + using System; + using System.ComponentModel.DataAnnotations; + + public class ConnectionStringType + { + /// + /// Gets or sets the identifier. + /// + /// + /// The identifier. + /// + [Key] + public Int32 Id { get; set; } + + /// + /// Gets or sets the description. + /// + /// + /// The description. + /// + [Required] + public String Description { get; set; } + } +} \ No newline at end of file diff --git a/Shared/EntityFramework/DbContextFactory.cs b/Shared/EntityFramework/DbContextFactory.cs new file mode 100644 index 00000000..c1befe7a --- /dev/null +++ b/Shared/EntityFramework/DbContextFactory.cs @@ -0,0 +1,78 @@ +namespace Shared.EntityFramework +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.EntityFrameworkCore; + using Repositories; + + public class DbContextFactory : IDbContextFactory where T : DbContext + { + #region Fields + + private readonly IConnectionStringConfigurationRepository ConnectionStringConfigurationRepository; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The connection string configuration repository. + /// The create context. + public DbContextFactory(IConnectionStringConfigurationRepository connectionStringConfigurationRepository, + Func createContext) + { + this.ConnectionStringConfigurationRepository = connectionStringConfigurationRepository; + this.CreateContext = createContext; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the context resolver. + /// + /// The context resolver. + private Func CreateContext { get; } + + #endregion + + #region Methods + + /// + /// Gets a context for the given identifier + /// + /// The identifier. + /// The cancellation token. + /// + /// DbContext. + /// + public virtual async Task GetContext(Guid identifier, + CancellationToken cancellationToken) + { + this.GuardIdentifier(identifier); + + String connectionString = + await this.ConnectionStringConfigurationRepository.GetConnectionString(identifier.ToString(), ConnectionStringType.ReadModel, cancellationToken); + return this.CreateContext(connectionString); + } + + /// + /// Guards the identifier. + /// + /// The identifier. + /// identifier + private void GuardIdentifier(Guid identifier) + { + if (identifier == Guid.Empty) + { + throw new ArgumentNullException(nameof(identifier)); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Shared/EntityFramework/IDbContextFactory.cs b/Shared/EntityFramework/IDbContextFactory.cs new file mode 100644 index 00000000..bb949838 --- /dev/null +++ b/Shared/EntityFramework/IDbContextFactory.cs @@ -0,0 +1,25 @@ +namespace Shared.EntityFramework +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.EntityFrameworkCore; + + public interface IDbContextFactory where T : DbContext + { + #region Methods + + /// + /// Gets a context for the given identifier + /// + /// The identifier. + /// The cancellation token. + /// + /// DbContext. + /// + Task GetContext(Guid identifier, + CancellationToken cancellationToken); + + #endregion + } +} \ No newline at end of file diff --git a/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.Designer.cs b/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.Designer.cs new file mode 100644 index 00000000..4f67f8fb --- /dev/null +++ b/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.Designer.cs @@ -0,0 +1,80 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shared.EntityFramework.ConnectionStringConfiguration; + +namespace Shared.Migrations +{ + [DbContext(typeof(ConnectionStringConfigurationContext))] + [Migration("20191119150009_InitialDatabase")] + partial class InitialDatabase + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnName("connectionString") + .HasColumnType("nvarchar(max)"); + + b.Property("ConnectionStringTypeId") + .HasColumnType("int"); + + b.Property("ExternalIdentifier") + .IsRequired() + .HasColumnName("externalIdentifier") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("ConnectionStringTypeId"); + + b.HasIndex("ExternalIdentifier", "ConnectionStringTypeId") + .IsUnique(); + + b.ToTable("ConnectionStringConfiguration"); + }); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ConnectionStringType"); + }); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringConfiguration", b => + { + b.HasOne("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringType", "ConnectionStringType") + .WithMany() + .HasForeignKey("ConnectionStringTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.cs b/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.cs new file mode 100644 index 00000000..7c09c578 --- /dev/null +++ b/Shared/EntityFramework/Migrations/20191119150009_InitialDatabase.cs @@ -0,0 +1,64 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Shared.Migrations +{ + public partial class InitialDatabase : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ConnectionStringType", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Description = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ConnectionStringType", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ConnectionStringConfiguration", + columns: table => new + { + Id = table.Column(nullable: false), + externalIdentifier = table.Column(nullable: false), + ConnectionStringTypeId = table.Column(nullable: false), + connectionString = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ConnectionStringConfiguration", x => x.Id); + table.ForeignKey( + name: "FK_ConnectionStringConfiguration_ConnectionStringType_ConnectionStringTypeId", + column: x => x.ConnectionStringTypeId, + principalTable: "ConnectionStringType", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ConnectionStringConfiguration_ConnectionStringTypeId", + table: "ConnectionStringConfiguration", + column: "ConnectionStringTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_ConnectionStringConfiguration_externalIdentifier_ConnectionStringTypeId", + table: "ConnectionStringConfiguration", + columns: new[] { "externalIdentifier", "ConnectionStringTypeId" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ConnectionStringConfiguration"); + + migrationBuilder.DropTable( + name: "ConnectionStringType"); + } + } +} diff --git a/Shared/EntityFramework/Migrations/ConnectionStringConfigurationContextModelSnapshot.cs b/Shared/EntityFramework/Migrations/ConnectionStringConfigurationContextModelSnapshot.cs new file mode 100644 index 00000000..4cad0c4a --- /dev/null +++ b/Shared/EntityFramework/Migrations/ConnectionStringConfigurationContextModelSnapshot.cs @@ -0,0 +1,78 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shared.EntityFramework.ConnectionStringConfiguration; + +namespace Shared.Migrations +{ + [DbContext(typeof(ConnectionStringConfigurationContext))] + partial class ConnectionStringConfigurationContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnName("connectionString") + .HasColumnType("nvarchar(max)"); + + b.Property("ConnectionStringTypeId") + .HasColumnType("int"); + + b.Property("ExternalIdentifier") + .IsRequired() + .HasColumnName("externalIdentifier") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("ConnectionStringTypeId"); + + b.HasIndex("ExternalIdentifier", "ConnectionStringTypeId") + .IsUnique(); + + b.ToTable("ConnectionStringConfiguration"); + }); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ConnectionStringType"); + }); + + modelBuilder.Entity("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringConfiguration", b => + { + b.HasOne("Shared.EntityFramework.ConnectionStringConfiguration.ConnectionStringType", "ConnectionStringType") + .WithMany() + .HasForeignKey("ConnectionStringTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Shared/Repositories/ConnectionStringConfigurationRepository.cs b/Shared/Repositories/ConnectionStringConfigurationRepository.cs new file mode 100644 index 00000000..8acc488c --- /dev/null +++ b/Shared/Repositories/ConnectionStringConfigurationRepository.cs @@ -0,0 +1,140 @@ +namespace Shared.Repositories +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using EntityFramework.ConnectionStringConfiguration; + using Exceptions; + using Microsoft.EntityFrameworkCore; + + public class ConnectionStringConfigurationRepository : IConnectionStringConfigurationRepository + { + #region Fields + + /// + /// The context resolver + /// + private readonly Func ContextResolver; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The context resolver. + public ConnectionStringConfigurationRepository(Func contextResolver) + { + this.ContextResolver = contextResolver; + } + + #endregion + + #region Methods + + /// + /// Deletes the given connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The cancellation token. + /// No configuration found for Connection String Identifier [{connectionStringIdentifier}] and DataModelType [{dataModelType}] + public async Task DeleteConnectionStringConfiguration(String externalIdentifier, ConnectionStringType connectionStringType, CancellationToken cancellationToken) + { + this.GuardAgainstNoExternalIdentifier(externalIdentifier); + + // Find the record in the config repository + using (ConnectionStringConfigurationContext context = this.ContextResolver()) + { + ConnectionStringConfiguration configuration = + await context.ConnectionStringConfiguration.SingleOrDefaultAsync(c => c.ExternalIdentifier == externalIdentifier && + c.ConnectionStringTypeId == (Int32)connectionStringType, cancellationToken); + + if (configuration == null) + { + throw new + NotFoundException($"No connection string configuration found for External Identifier [{externalIdentifier}]"); + } + + context.Remove(configuration); + + await context.SaveChangesAsync(cancellationToken); + } + } + + /// + /// Gets the connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The cancellation token. + /// + /// No configuration found for Connection String Identifier [{connectionStringIdentifier}] and DataModelType [{dataModelType}] + public async Task GetConnectionString(String externalIdentifier, ConnectionStringType connectionStringType, CancellationToken cancellationToken) + { + this.GuardAgainstNoExternalIdentifier(externalIdentifier); + + // Find the record in the config repository + using (ConnectionStringConfigurationContext context = this.ContextResolver()) + { + ConnectionStringConfiguration configuration = + await context.ConnectionStringConfiguration.SingleOrDefaultAsync(c => c.ExternalIdentifier == externalIdentifier && + c.ConnectionStringTypeId == (Int32)connectionStringType, + cancellationToken); + + if (configuration == null) + { + throw new + NotFoundException($"No connection string configuration found for External Identifier [{externalIdentifier}] and Connection String Type [{connectionStringType}]"); + } + + return configuration.ConnectionString; + } + } + + /// + /// Creates the connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The connection string. + /// The cancellation token. + public async Task CreateConnectionString(String externalIdentifier, ConnectionStringType connectionStringType, String connectionString, CancellationToken cancellationToken) + { + this.GuardAgainstNoExternalIdentifier(externalIdentifier); + + // Find the record in the config repository + using (ConnectionStringConfigurationContext context = this.ContextResolver()) + { + ConnectionStringConfiguration configuration = new ConnectionStringConfiguration + { + ExternalIdentifier = externalIdentifier, + ConnectionString = connectionString, + ConnectionStringTypeId = (Int32)connectionStringType + }; + + await context.AddAsync(configuration, cancellationToken); + + await context.SaveChangesAsync(cancellationToken); + } + } + + /// + /// Guards the against no connection string identifier. + /// + /// The external identifier. + /// Value cannot be empty. - connectionStringIdentifier + /// Value cannot be empty. - connectionStringIdentifier + private void GuardAgainstNoExternalIdentifier(String externalIdentifier) + { + //Check if the external Id is present + if (String.IsNullOrWhiteSpace(externalIdentifier)) + { + throw new ArgumentException("Value cannot be empty.", nameof(externalIdentifier)); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Shared/Repositories/ConnectionStringType.cs b/Shared/Repositories/ConnectionStringType.cs new file mode 100644 index 00000000..3badc6b1 --- /dev/null +++ b/Shared/Repositories/ConnectionStringType.cs @@ -0,0 +1,8 @@ +namespace Shared.Repositories +{ + public enum ConnectionStringType + { + EventStore = 1, + ReadModel = 2 + } +} \ No newline at end of file diff --git a/Shared/Repositories/IConnectionStringConfigurationRepository.cs b/Shared/Repositories/IConnectionStringConfigurationRepository.cs new file mode 100644 index 00000000..62f23b25 --- /dev/null +++ b/Shared/Repositories/IConnectionStringConfigurationRepository.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Shared.Repositories +{ + using System.Threading; + using System.Threading.Tasks; + + public interface IConnectionStringConfigurationRepository + { + #region Methods + + /// + /// Deletes the given connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The cancellation token. + /// + Task DeleteConnectionStringConfiguration(String externalIdentifier, ConnectionStringType connectionStringType, CancellationToken cancellationToken); + + /// + /// Gets the connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The cancellation token. + /// + Task GetConnectionString(String externalIdentifier, ConnectionStringType connectionStringType, CancellationToken cancellationToken); + + /// + /// Creates the connection string. + /// + /// The external identifier. + /// Type of the connection string. + /// The connection string. + /// The cancellation token. + /// + Task CreateConnectionString(String externalIdentifier, ConnectionStringType connectionStringType, String connectionString, CancellationToken cancellationToken); + + #endregion + } +} diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 452c6b9c..7cc39906 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -13,6 +13,9 @@ + + +