diff --git a/src/ServiceControl.Persistence.Sql.Core/Implementation/LicensingDataStore.cs b/src/ServiceControl.Persistence.Sql.Core/Implementation/LicensingDataStore.cs index f6fcfb74e4..89ff1bbeae 100644 --- a/src/ServiceControl.Persistence.Sql.Core/Implementation/LicensingDataStore.cs +++ b/src/ServiceControl.Persistence.Sql.Core/Implementation/LicensingDataStore.cs @@ -24,7 +24,9 @@ public async Task>> GetEndpointT var cutOff = DefaultCutOff(); - var data = await dbContext.Throughput.Where(x => queueNames.Contains(x.EndpointName) && x.Date >= cutOff) + var data = await dbContext.Throughput + .AsNoTracking() + .Where(x => queueNames.Contains(x.EndpointName) && x.Date >= cutOff) .ToListAsync(cancellationToken); var lookup = data.ToLookup(x => x.EndpointName); @@ -170,28 +172,43 @@ public async Task UpdateUserIndicatorOnEndpoints(List userI using var scope = serviceProvider.CreateScope(); using var dbContext = scope.ServiceProvider.GetRequiredService(); + // Get all relevant sanitized names from endpoints matched by name + var sanitizedNames = await dbContext.Endpoints + .Where(e => updates.Keys.Contains(e.EndpointName) && e.SanitizedEndpointName != null) + .Select(e => e.SanitizedEndpointName) + .Distinct() + .ToListAsync(cancellationToken); + + // Get all endpoints that match either by name or sanitized name in a single query var endpoints = await dbContext.Endpoints - .Where(e => updates.Keys.Contains(e.EndpointName) || (e.SanitizedEndpointName != null && updates.Keys.Contains(e.SanitizedEndpointName))) + .Where(e => updates.Keys.Contains(e.EndpointName) + || (e.SanitizedEndpointName != null && updates.Keys.Contains(e.SanitizedEndpointName)) + || (e.SanitizedEndpointName != null && sanitizedNames.Contains(e.SanitizedEndpointName))) .ToListAsync(cancellationToken) ?? []; foreach (var endpoint in endpoints) { if (endpoint.SanitizedEndpointName is not null && updates.TryGetValue(endpoint.SanitizedEndpointName, out var newValueFromSanitizedName)) { + // Direct match by sanitized name endpoint.UserIndicator = newValueFromSanitizedName; } else if (updates.TryGetValue(endpoint.EndpointName, out var newValueFromEndpoint)) { + // Direct match by endpoint name - this should also update all endpoints with the same sanitized name endpoint.UserIndicator = newValueFromEndpoint; - //update all that match this sanitized name - var sanitizedMatchingEndpoints = await dbContext.Endpoints - .Where(e => e.SanitizedEndpointName == endpoint.SanitizedEndpointName && e.EndpointName != endpoint.EndpointName) - .ToListAsync(cancellationToken) ?? []; + } + else if (endpoint.SanitizedEndpointName != null && sanitizedNames.Contains(endpoint.SanitizedEndpointName)) + { + // This endpoint shares a sanitized name with an endpoint that was matched by name + // Find the update value from the endpoint that has this sanitized name + var matchingEndpoint = endpoints.FirstOrDefault(e => + e.SanitizedEndpointName == endpoint.SanitizedEndpointName && + updates.ContainsKey(e.EndpointName)); - foreach (var matchingEndpointOnSanitizedName in sanitizedMatchingEndpoints) + if (matchingEndpoint != null && updates.TryGetValue(matchingEndpoint.EndpointName, out var cascadedValue)) { - matchingEndpointOnSanitizedName.UserIndicator = newValueFromEndpoint; - _ = dbContext.Endpoints.Update(matchingEndpointOnSanitizedName); + endpoint.UserIndicator = cascadedValue; } } _ = dbContext.Endpoints.Update(endpoint); @@ -263,7 +280,9 @@ public async Task GetBrokerMetadata(CancellationToken cancellati { using var scope = serviceProvider.CreateScope(); await using var dbContext = scope.ServiceProvider.GetRequiredService(); - var existing = await dbContext.LicensingMetadata.SingleOrDefaultAsync(m => m.Key == key, cancellationToken); + var existing = await dbContext.LicensingMetadata + .AsNoTracking() + .SingleOrDefaultAsync(m => m.Key == key, cancellationToken); if (existing is null) { return default; diff --git a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20241208000000_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20241208000000_InitialCreate.cs deleted file mode 100644 index 160fcdabd5..0000000000 --- a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20241208000000_InitialCreate.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ServiceControl.Persistence.Sql.MySQL.Migrations; - -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -/// -public partial class InitialCreate : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TrialLicense", - columns: table => new - { - Id = table.Column(type: "int", nullable: false, defaultValue: 1), - TrialEndDate = table.Column(type: "date", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TrialLicense", x => x.Id); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TrialLicense"); - } -} diff --git a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.Designer.cs b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.Designer.cs new file mode 100644 index 0000000000..e1129e6700 --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.Designer.cs @@ -0,0 +1,143 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using ServiceControl.Persistence.Sql.MySQL; + +#nullable disable + +namespace ServiceControl.Persistence.Sql.MySQL.Migrations +{ + [DbContext(typeof(MySqlDbContext))] + [Migration("20251210040740_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("MessageCount") + .HasColumnType("bigint"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("longtext"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("LastCollectedData") + .HasColumnType("date"); + + b.Property("SanitizedEndpointName") + .HasColumnType("longtext"); + + b.Property("Scope") + .HasColumnType("longtext"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UserIndicator") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => + { + b.Property("Id") + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("TrialEndDate") + .HasColumnType("date"); + + b.HasKey("Id"); + + b.ToTable("TrialLicense", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.cs new file mode 100644 index 0000000000..c1d7bfa3fc --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/20251210040740_InitialCreate.cs @@ -0,0 +1,128 @@ +#nullable disable + +namespace ServiceControl.Persistence.Sql.MySQL.Migrations +{ + using System; + using Microsoft.EntityFrameworkCore.Metadata; + using Microsoft.EntityFrameworkCore.Migrations; + + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "DailyThroughput", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + EndpointName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ThroughputSource = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Date = table.Column(type: "date", nullable: false), + MessageCount = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DailyThroughput", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "LicensingMetadata", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Key = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Data = table.Column(type: "varchar(2000)", maxLength: 2000, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_LicensingMetadata", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ThroughputEndpoint", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + EndpointName = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ThroughputSource = table.Column(type: "varchar(50)", maxLength: 50, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + SanitizedEndpointName = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + EndpointIndicators = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + UserIndicator = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Scope = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + LastCollectedData = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ThroughputEndpoint", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "TrialLicense", + columns: table => new + { + Id = table.Column(type: "int", nullable: false, defaultValue: 1), + TrialEndDate = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrialLicense", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "UC_DailyThroughput_EndpointName_ThroughputSource_Date", + table: "DailyThroughput", + columns: new[] { "EndpointName", "ThroughputSource", "Date" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LicensingMetadata_Key", + table: "LicensingMetadata", + column: "Key", + unique: true); + + migrationBuilder.CreateIndex( + name: "UC_ThroughputEndpoint_EndpointName_ThroughputSource", + table: "ThroughputEndpoint", + columns: new[] { "EndpointName", "ThroughputSource" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DailyThroughput"); + + migrationBuilder.DropTable( + name: "LicensingMetadata"); + + migrationBuilder.DropTable( + name: "ThroughputEndpoint"); + + migrationBuilder.DropTable( + name: "TrialLicense"); + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/MySqlDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/MySqlDbContextModelSnapshot.cs index 182e94615d..df1713a0f5 100644 --- a/src/ServiceControl.Persistence.Sql.MySQL/Migrations/MySqlDbContextModelSnapshot.cs +++ b/src/ServiceControl.Persistence.Sql.MySQL/Migrations/MySqlDbContextModelSnapshot.cs @@ -1,6 +1,9 @@ -// +// +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using ServiceControl.Persistence.Sql.MySQL; #nullable disable @@ -19,6 +22,105 @@ protected override void BuildModel(ModelBuilder modelBuilder) MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("MessageCount") + .HasColumnType("bigint"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("longtext"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("LastCollectedData") + .HasColumnType("date"); + + b.Property("SanitizedEndpointName") + .HasColumnType("longtext"); + + b.Property("Scope") + .HasColumnType("longtext"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("UserIndicator") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => { b.Property("Id") diff --git a/src/ServiceControl.Persistence.Sql.MySQL/MySqlDbContextFactory.cs b/src/ServiceControl.Persistence.Sql.MySQL/MySqlDbContextFactory.cs index 539612142d..2cccacee55 100644 --- a/src/ServiceControl.Persistence.Sql.MySQL/MySqlDbContextFactory.cs +++ b/src/ServiceControl.Persistence.Sql.MySQL/MySqlDbContextFactory.cs @@ -12,7 +12,8 @@ public MySqlDbContext CreateDbContext(string[] args) // Use a dummy connection string for design-time operations var connectionString = "Server=localhost;Database=servicecontrol;User=root;Password=mysql"; - optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)); + // Use MySQL 8.0 as the server version for migrations + optionsBuilder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0))); return new MySqlDbContext(optionsBuilder.Options); } diff --git a/src/ServiceControl.Persistence.Sql.MySQL/MySqlPersistence.cs b/src/ServiceControl.Persistence.Sql.MySQL/MySqlPersistence.cs index 08a70a5671..c4207dff30 100644 --- a/src/ServiceControl.Persistence.Sql.MySQL/MySqlPersistence.cs +++ b/src/ServiceControl.Persistence.Sql.MySQL/MySqlPersistence.cs @@ -5,6 +5,7 @@ namespace ServiceControl.Persistence.Sql.MySQL; using Core.Implementation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Particular.LicensingComponent.Persistence; using ServiceControl.Persistence; class MySqlPersistence : IPersistence @@ -26,6 +27,7 @@ public void AddPersistence(IServiceCollection services) } services.AddSingleton(); + services.AddSingleton(); } public void AddInstaller(IServiceCollection services) diff --git a/src/ServiceControl.Persistence.Sql.MySQL/ServiceControl.Persistence.Sql.MySQL.csproj b/src/ServiceControl.Persistence.Sql.MySQL/ServiceControl.Persistence.Sql.MySQL.csproj index 5cdc8c7401..d5ea500c83 100644 --- a/src/ServiceControl.Persistence.Sql.MySQL/ServiceControl.Persistence.Sql.MySQL.csproj +++ b/src/ServiceControl.Persistence.Sql.MySQL/ServiceControl.Persistence.Sql.MySQL.csproj @@ -14,6 +14,7 @@ + diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20241208000000_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20241208000000_InitialCreate.cs deleted file mode 100644 index b830a81b63..0000000000 --- a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20241208000000_InitialCreate.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ServiceControl.Persistence.Sql.PostgreSQL.Migrations; - -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -/// -public partial class InitialCreate : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "triallicense", - columns: table => new - { - id = table.Column(type: "integer", nullable: false, defaultValue: 1), - trialenddate = table.Column(type: "date", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_triallicense", x => x.id); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "triallicense"); - } -} diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.Designer.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.Designer.cs new file mode 100644 index 0000000000..5bfef70c40 --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.Designer.cs @@ -0,0 +1,165 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using ServiceControl.Persistence.Sql.PostgreSQL; + +#nullable disable + +namespace ServiceControl.Persistence.Sql.PostgreSQL.Migrations +{ + [DbContext(typeof(PostgreSqlDbContext))] + [Migration("20251210071033_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date") + .HasColumnName("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("endpoint_name"); + + b.Property("MessageCount") + .HasColumnType("bigint") + .HasColumnName("message_count"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("throughput_source"); + + b.HasKey("Id") + .HasName("p_k_throughput"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("data"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("key"); + + b.HasKey("Id") + .HasName("p_k_licensing_metadata"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("text") + .HasColumnName("endpoint_indicators"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("endpoint_name"); + + b.Property("LastCollectedData") + .HasColumnType("date") + .HasColumnName("last_collected_data"); + + b.Property("SanitizedEndpointName") + .HasColumnType("text") + .HasColumnName("sanitized_endpoint_name"); + + b.Property("Scope") + .HasColumnType("text") + .HasColumnName("scope"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("throughput_source"); + + b.Property("UserIndicator") + .HasColumnType("text") + .HasColumnName("user_indicator"); + + b.HasKey("Id") + .HasName("p_k_endpoints"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("id"); + + b.Property("TrialEndDate") + .HasColumnType("date") + .HasColumnName("trial_end_date"); + + b.HasKey("Id") + .HasName("p_k_trial_licenses"); + + b.ToTable("TrialLicense", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.cs new file mode 100644 index 0000000000..dd5271dda5 --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/20251210071033_InitialCreate.cs @@ -0,0 +1,111 @@ +#nullable disable + +namespace ServiceControl.Persistence.Sql.PostgreSQL.Migrations +{ + using System; + using Microsoft.EntityFrameworkCore.Migrations; + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DailyThroughput", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + endpoint_name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + throughput_source = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + date = table.Column(type: "date", nullable: false), + message_count = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("p_k_throughput", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "LicensingMetadata", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + data = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("p_k_licensing_metadata", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "ThroughputEndpoint", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + endpoint_name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + throughput_source = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + sanitized_endpoint_name = table.Column(type: "text", nullable: true), + endpoint_indicators = table.Column(type: "text", nullable: true), + user_indicator = table.Column(type: "text", nullable: true), + scope = table.Column(type: "text", nullable: true), + last_collected_data = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("p_k_endpoints", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "TrialLicense", + columns: table => new + { + id = table.Column(type: "integer", nullable: false, defaultValue: 1), + trial_end_date = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("p_k_trial_licenses", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "UC_DailyThroughput_EndpointName_ThroughputSource_Date", + table: "DailyThroughput", + columns: new[] { "endpoint_name", "throughput_source", "date" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LicensingMetadata_key", + table: "LicensingMetadata", + column: "key", + unique: true); + + migrationBuilder.CreateIndex( + name: "UC_ThroughputEndpoint_EndpointName_ThroughputSource", + table: "ThroughputEndpoint", + columns: new[] { "endpoint_name", "throughput_source" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DailyThroughput"); + + migrationBuilder.DropTable( + name: "LicensingMetadata"); + + migrationBuilder.DropTable( + name: "ThroughputEndpoint"); + + migrationBuilder.DropTable( + name: "TrialLicense"); + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/PostgreSqlDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/PostgreSqlDbContextModelSnapshot.cs index f342bfcb71..ff49b46e96 100644 --- a/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/PostgreSqlDbContextModelSnapshot.cs +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/Migrations/PostgreSqlDbContextModelSnapshot.cs @@ -1,6 +1,9 @@ -// +// +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using ServiceControl.Persistence.Sql.PostgreSQL; #nullable disable @@ -19,20 +22,139 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date") + .HasColumnName("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("endpoint_name"); + + b.Property("MessageCount") + .HasColumnType("bigint") + .HasColumnName("message_count"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("throughput_source"); + + b.HasKey("Id") + .HasName("p_k_throughput"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("data"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("key"); + + b.HasKey("Id") + .HasName("p_k_licensing_metadata"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("text") + .HasColumnName("endpoint_indicators"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("endpoint_name"); + + b.Property("LastCollectedData") + .HasColumnType("date") + .HasColumnName("last_collected_data"); + + b.Property("SanitizedEndpointName") + .HasColumnType("text") + .HasColumnName("sanitized_endpoint_name"); + + b.Property("Scope") + .HasColumnType("text") + .HasColumnName("scope"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("throughput_source"); + + b.Property("UserIndicator") + .HasColumnType("text") + .HasColumnName("user_indicator"); + + b.HasKey("Id") + .HasName("p_k_endpoints"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => { b.Property("Id") .HasColumnType("integer") - .HasColumnName("id") - .HasDefaultValue(1); + .HasDefaultValue(1) + .HasColumnName("id"); b.Property("TrialEndDate") .HasColumnType("date") - .HasColumnName("trialenddate"); + .HasColumnName("trial_end_date"); - b.HasKey("Id"); + b.HasKey("Id") + .HasName("p_k_trial_licenses"); - b.ToTable("triallicense", (string)null); + b.ToTable("TrialLicense", (string)null); }); #pragma warning restore 612, 618 } diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlDbContext.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlDbContext.cs index c49a0e387f..3d435d487b 100644 --- a/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlDbContext.cs +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlDbContext.cs @@ -11,20 +11,55 @@ public PostgreSqlDbContext(DbContextOptions options) : base protected override void OnModelCreating(ModelBuilder modelBuilder) { - // Apply lowercase naming convention for PostgreSQL + // Apply snake_case naming convention for PostgreSQL foreach (var entity in modelBuilder.Model.GetEntityTypes()) { - entity.SetTableName(entity.GetTableName()?.ToLowerInvariant()); + entity.SetTableName(ToSnakeCase(entity.GetTableName())); foreach (var property in entity.GetProperties()) { - property.SetColumnName(property.GetColumnName().ToLowerInvariant()); + property.SetColumnName(ToSnakeCase(property.GetColumnName())); + } + + foreach (var key in entity.GetKeys()) + { + key.SetName(ToSnakeCase(key.GetName())); + } + + foreach (var foreignKey in entity.GetForeignKeys()) + { + foreignKey.SetConstraintName(ToSnakeCase(foreignKey.GetConstraintName())); + } + + foreach (var index in entity.GetIndexes()) + { + index.SetDatabaseName(ToSnakeCase(index.GetDatabaseName())); } } base.OnModelCreating(modelBuilder); } + static string? ToSnakeCase(string? name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + + var builder = new System.Text.StringBuilder(); + for (var i = 0; i < name.Length; i++) + { + var c = name[i]; + if (char.IsUpper(c) && i > 0) + { + builder.Append('_'); + } + builder.Append(char.ToLowerInvariant(c)); + } + return builder.ToString(); + } + protected override void OnModelCreatingProvider(ModelBuilder modelBuilder) { // PostgreSQL-specific configurations if needed diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlPersistence.cs b/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlPersistence.cs index 7bbc1b9ae2..72fa7d731e 100644 --- a/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlPersistence.cs +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/PostgreSqlPersistence.cs @@ -5,6 +5,7 @@ namespace ServiceControl.Persistence.Sql.PostgreSQL; using Core.Implementation; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Particular.LicensingComponent.Persistence; using ServiceControl.Persistence; class PostgreSqlPersistence : IPersistence @@ -26,6 +27,7 @@ public void AddPersistence(IServiceCollection services) } services.AddSingleton(); + services.AddSingleton(); } public void AddInstaller(IServiceCollection services) diff --git a/src/ServiceControl.Persistence.Sql.PostgreSQL/ServiceControl.Persistence.Sql.PostgreSQL.csproj b/src/ServiceControl.Persistence.Sql.PostgreSQL/ServiceControl.Persistence.Sql.PostgreSQL.csproj index 3add7d15e1..275e2387eb 100644 --- a/src/ServiceControl.Persistence.Sql.PostgreSQL/ServiceControl.Persistence.Sql.PostgreSQL.csproj +++ b/src/ServiceControl.Persistence.Sql.PostgreSQL/ServiceControl.Persistence.Sql.PostgreSQL.csproj @@ -14,6 +14,7 @@ + diff --git a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20241208000000_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20241208000000_InitialCreate.cs deleted file mode 100644 index eed369f43e..0000000000 --- a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20241208000000_InitialCreate.cs +++ /dev/null @@ -1,102 +0,0 @@ -namespace ServiceControl.Persistence.Sql.SqlServer.Migrations; - -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -/// -[DbContext(typeof(SqlServerDbContext))] -[Migration("20241208000000_InitialCreate")] -public partial class InitialCreate : Migration -{ - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TrialLicense", - columns: table => new - { - Id = table.Column(type: "int", nullable: false, defaultValue: 1), - TrialEndDate = table.Column(type: "date", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TrialLicense", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "LicensingMetadata", - columns: table => new - { - Id = table.Column(type: "int", nullable: false).Annotation("SqlServer:Identity", "1, 1"), - Key = table.Column(type: "nvarchar(200)", nullable: false), - Data = table.Column(type: "nvarchar(2000)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_LicensingMetadata", x => x.Id); - table.UniqueConstraint("UC_LicensingMetadata_Key", t => t.Key); - }); - - migrationBuilder.CreateTable( - name: "ThroughputEndpoint", - columns: table => new - { - Id = table.Column(type: "int", nullable: false).Annotation("SqlServer:Identity", "1, 1"), - EndpointName = table.Column(type: "nvarchar(200)", nullable: false), - ThroughputSource = table.Column(type: "nvarchar(50)", nullable: false), - SanitizedEndpointName = table.Column(type: "nvarchar(200)", nullable: true), - EndpointIndicators = table.Column(type: "nvarchar(MAX)", nullable: true), - UserIndicator = table.Column(type: "nvarchar(50)", nullable: true), - Scope = table.Column(type: "nvarchar(MAX)", nullable: true), - LastCollectedData = table.Column(type: "date", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ThrouhgputEndpoint", x => x.Id); - table.UniqueConstraint("UC_ThroughputEndpoint_EndpointName_ThroughputSource", t => new - { - t.EndpointName, - t.ThroughputSource - }); - } - ); - - migrationBuilder.CreateTable( - name: "DailyThroughput", - columns: table => new - { - Id = table.Column(type: "int", nullable: false).Annotation("SqlServer:Identity", "1, 1"), - EndpointName = table.Column(type: "nvarchar(200)", nullable: false), - ThroughputSource = table.Column(type: "nvarchar(50)", nullable: false), - Date = table.Column(type: "date", nullable: false), - MessageCount = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DailyThroughput", x => x.Id); - table.UniqueConstraint("UC_DailyThroughput_EndpointName_ThroughputSource_Date", e => new - { - e.EndpointName, - e.ThroughputSource, - e.Date - }); - } - ); - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TrialLicense"); - migrationBuilder.DropTable( - name: "LicensingMetadata"); - migrationBuilder.DropTable( - name: "ThroughputEndpoint"); - migrationBuilder.DropTable( - name: "DailyThroughput"); - } -} \ No newline at end of file diff --git a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.Designer.cs b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.Designer.cs new file mode 100644 index 0000000000..92dd16614b --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.Designer.cs @@ -0,0 +1,143 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using ServiceControl.Persistence.Sql.SqlServer; + +#nullable disable + +namespace ServiceControl.Persistence.Sql.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerDbContext))] + [Migration("20251210040736_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("MessageCount") + .HasColumnType("bigint"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("nvarchar(max)"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastCollectedData") + .HasColumnType("date"); + + b.Property("SanitizedEndpointName") + .HasColumnType("nvarchar(max)"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UserIndicator") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => + { + b.Property("Id") + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("TrialEndDate") + .HasColumnType("date"); + + b.HasKey("Id"); + + b.ToTable("TrialLicense", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.cs b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.cs new file mode 100644 index 0000000000..b32f1f2962 --- /dev/null +++ b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/20251210040736_InitialCreate.cs @@ -0,0 +1,110 @@ +#nullable disable + +namespace ServiceControl.Persistence.Sql.SqlServer.Migrations +{ + using System; + using Microsoft.EntityFrameworkCore.Migrations; + + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DailyThroughput", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + EndpointName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + ThroughputSource = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Date = table.Column(type: "date", nullable: false), + MessageCount = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DailyThroughput", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LicensingMetadata", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Key = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Data = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LicensingMetadata", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ThroughputEndpoint", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + EndpointName = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + ThroughputSource = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + SanitizedEndpointName = table.Column(type: "nvarchar(max)", nullable: true), + EndpointIndicators = table.Column(type: "nvarchar(max)", nullable: true), + UserIndicator = table.Column(type: "nvarchar(max)", nullable: true), + Scope = table.Column(type: "nvarchar(max)", nullable: true), + LastCollectedData = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ThroughputEndpoint", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TrialLicense", + columns: table => new + { + Id = table.Column(type: "int", nullable: false, defaultValue: 1), + TrialEndDate = table.Column(type: "date", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrialLicense", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "UC_DailyThroughput_EndpointName_ThroughputSource_Date", + table: "DailyThroughput", + columns: new[] { "EndpointName", "ThroughputSource", "Date" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LicensingMetadata_Key", + table: "LicensingMetadata", + column: "Key", + unique: true); + + migrationBuilder.CreateIndex( + name: "UC_ThroughputEndpoint_EndpointName_ThroughputSource", + table: "ThroughputEndpoint", + columns: new[] { "EndpointName", "ThroughputSource" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DailyThroughput"); + + migrationBuilder.DropTable( + name: "LicensingMetadata"); + + migrationBuilder.DropTable( + name: "ThroughputEndpoint"); + + migrationBuilder.DropTable( + name: "TrialLicense"); + } + } +} diff --git a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs index b994999482..576cf28f6d 100644 --- a/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs +++ b/src/ServiceControl.Persistence.Sql.SqlServer/Migrations/SqlServerDbContextModelSnapshot.cs @@ -1,6 +1,9 @@ -// +// +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using ServiceControl.Persistence.Sql.SqlServer; #nullable disable @@ -19,6 +22,105 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.DailyThroughputEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("MessageCount") + .HasColumnType("bigint"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource", "Date" }, "UC_DailyThroughput_EndpointName_ThroughputSource_Date") + .IsUnique(); + + b.ToTable("DailyThroughput", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.LicensingMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("LicensingMetadata", (string)null); + }); + + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.ThroughputEndpointEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EndpointIndicators") + .HasColumnType("nvarchar(max)"); + + b.Property("EndpointName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastCollectedData") + .HasColumnType("date"); + + b.Property("SanitizedEndpointName") + .HasColumnType("nvarchar(max)"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.Property("ThroughputSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UserIndicator") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EndpointName", "ThroughputSource" }, "UC_ThroughputEndpoint_EndpointName_ThroughputSource") + .IsUnique(); + + b.ToTable("ThroughputEndpoint", (string)null); + }); + modelBuilder.Entity("ServiceControl.Persistence.Sql.Core.Entities.TrialLicenseEntity", b => { b.Property("Id") diff --git a/src/ServiceControl.Persistence.Tests.Sql.SqlServer/PersistenceTestsContext.cs b/src/ServiceControl.Persistence.Tests.Sql.SqlServer/PersistenceTestsContext.cs index 9004a16a50..c4f18a4e90 100644 --- a/src/ServiceControl.Persistence.Tests.Sql.SqlServer/PersistenceTestsContext.cs +++ b/src/ServiceControl.Persistence.Tests.Sql.SqlServer/PersistenceTestsContext.cs @@ -15,7 +15,7 @@ public class PersistenceTestsContext : IPersistenceTestsContext { IHost host; - public async Task Setup(IHostApplicationBuilder hostBuilder) + public Task Setup(IHostApplicationBuilder hostBuilder) { PersistenceSettings = new SqlServerPersisterSettings { @@ -27,6 +27,8 @@ public async Task Setup(IHostApplicationBuilder hostBuilder) var persistence = new SqlServerPersistenceConfiguration().Create(PersistenceSettings); persistence.AddPersistence(hostBuilder.Services); persistence.AddInstaller(hostBuilder.Services); + + return Task.CompletedTask; } public async Task PostSetup(IHost host)