diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.Designer.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.Designer.cs
deleted file mode 100644
index 1a1383b3e2..0000000000
--- a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.Designer.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-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.EFCore.PostgreSql;
-
-#nullable disable
-
-namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
-{
- [DbContext(typeof(PostgreSqlServiceControlDbContext))]
- [Migration("20260720230745_Initial")]
- partial class Initial
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "10.0.9")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
- {
- b.Property("Id")
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property("Host")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("host");
-
- b.Property("HostId")
- .HasColumnType("uuid")
- .HasColumnName("host_id");
-
- b.Property("Monitored")
- .HasColumnType("boolean")
- .HasColumnName("monitored");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("name");
-
- b.HasKey("Id");
-
- b.ToTable("known_endpoints", (string)null);
- });
-
- modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointInsertOnlyEntity", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("bigint")
- .HasColumnName("id");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
-
- b.Property("Host")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("host");
-
- b.Property("HostId")
- .HasColumnType("uuid")
- .HasColumnName("host_id");
-
- b.Property("KnownEndpointId")
- .HasColumnType("uuid")
- .HasColumnName("known_endpoint_id");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("text")
- .HasColumnName("name");
-
- b.HasKey("Id");
-
- b.HasIndex("KnownEndpointId");
-
- b.ToTable("known_endpoints_insert_only", (string)null);
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.cs
deleted file mode 100644
index d2caaa189a..0000000000
--- a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260720230745_Initial.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
-{
- ///
- public partial class Initial : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "known_endpoints",
- columns: table => new
- {
- id = table.Column(type: "uuid", nullable: false),
- name = table.Column(type: "text", nullable: false),
- host_id = table.Column(type: "uuid", nullable: false),
- host = table.Column(type: "text", nullable: false),
- monitored = table.Column(type: "boolean", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_known_endpoints", x => x.id);
- });
-
- migrationBuilder.CreateTable(
- name: "known_endpoints_insert_only",
- columns: table => new
- {
- id = table.Column(type: "bigint", nullable: false)
- .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
- known_endpoint_id = table.Column(type: "uuid", nullable: false),
- name = table.Column(type: "text", nullable: false),
- host_id = table.Column(type: "uuid", nullable: false),
- host = table.Column(type: "text", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_known_endpoints_insert_only", x => x.id);
- });
-
- migrationBuilder.CreateIndex(
- name: "IX_known_endpoints_insert_only_known_endpoint_id",
- table: "known_endpoints_insert_only",
- column: "known_endpoint_id");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "known_endpoints");
-
- migrationBuilder.DropTable(
- name: "known_endpoints_insert_only");
- }
- }
-}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.Designer.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.Designer.cs
new file mode 100644
index 0000000000..53725d994f
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.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.EFCore.PostgreSql;
+
+#nullable disable
+
+namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
+{
+ [DbContext(typeof(PostgreSqlServiceControlDbContext))]
+ [Migration("20260722052903_Initial")]
+ partial class Initial
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name");
+
+ b.Property("TrackInstances")
+ .HasColumnType("boolean")
+ .HasColumnName("track_instances");
+
+ b.HasKey("Name")
+ .HasName("pk_endpoint_settings");
+
+ b.ToTable("EndpointSettings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("EndpointAddress")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("endpoint_address");
+
+ b.Property("OriginalMessageIdentifier")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("original_message_identifier");
+
+ b.HasKey("Id")
+ .HasName("pk_failed_messages");
+
+ b.HasIndex("OriginalMessageIdentifier", "EndpointAddress")
+ .HasDatabaseName("ix_failed_messages_original_message_identifier_endpoint_address");
+
+ b.ToTable("FailedMessages", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Host")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("host");
+
+ b.Property("HostId")
+ .HasColumnType("uuid")
+ .HasColumnName("host_id");
+
+ b.Property("Monitored")
+ .HasColumnType("boolean")
+ .HasColumnName("monitored");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("pk_known_endpoints");
+
+ b.ToTable("KnownEndpoints", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointInsertOnlyEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Host")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("host");
+
+ b.Property("HostId")
+ .HasColumnType("uuid")
+ .HasColumnName("host_id");
+
+ b.Property("KnownEndpointId")
+ .HasColumnType("uuid")
+ .HasColumnName("known_endpoint_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id")
+ .HasName("pk_known_endpoints_insert_only");
+
+ b.HasIndex("KnownEndpointId")
+ .HasDatabaseName("ix_known_endpoints_insert_only_known_endpoint_id");
+
+ b.ToTable("KnownEndpointsInsertOnly", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.TrialMetadataEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("TrialEndDate")
+ .HasColumnType("date")
+ .HasColumnName("trial_end_date");
+
+ b.HasKey("Id")
+ .HasName("pk_trial_metadata");
+
+ b.ToTable("trial_metadata", (string)null);
+
+ b.HasData(
+ new
+ {
+ Id = 1
+ });
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.cs
new file mode 100644
index 0000000000..0059248dea
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/20260722052903_Initial.cs
@@ -0,0 +1,119 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace ServiceControl.Persistence.EFCore.PostgreSql.Migrations
+{
+ ///
+ public partial class Initial : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "EndpointSettings",
+ columns: table => new
+ {
+ name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
+ track_instances = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_endpoint_settings", x => x.name);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "FailedMessages",
+ columns: table => new
+ {
+ id = table.Column(type: "uuid", nullable: false),
+ original_message_identifier = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
+ endpoint_address = table.Column(type: "character varying(200)", maxLength: 200, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_failed_messages", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "KnownEndpoints",
+ columns: table => new
+ {
+ id = table.Column(type: "uuid", nullable: false),
+ name = table.Column(type: "text", nullable: false),
+ host_id = table.Column(type: "uuid", nullable: false),
+ host = table.Column(type: "text", nullable: false),
+ monitored = table.Column(type: "boolean", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_known_endpoints", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "KnownEndpointsInsertOnly",
+ columns: table => new
+ {
+ id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ known_endpoint_id = table.Column(type: "uuid", nullable: false),
+ name = table.Column(type: "text", nullable: false),
+ host_id = table.Column(type: "uuid", nullable: false),
+ host = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_known_endpoints_insert_only", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "trial_metadata",
+ columns: table => new
+ {
+ id = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ trial_end_date = table.Column(type: "date", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_trial_metadata", x => x.id);
+ });
+
+ migrationBuilder.InsertData(
+ table: "trial_metadata",
+ columns: new[] { "id", "trial_end_date" },
+ values: new object[] { 1, null });
+
+ migrationBuilder.CreateIndex(
+ name: "ix_failed_messages_original_message_identifier_endpoint_address",
+ table: "FailedMessages",
+ columns: new[] { "original_message_identifier", "endpoint_address" });
+
+ migrationBuilder.CreateIndex(
+ name: "ix_known_endpoints_insert_only_known_endpoint_id",
+ table: "KnownEndpointsInsertOnly",
+ column: "known_endpoint_id");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "EndpointSettings");
+
+ migrationBuilder.DropTable(
+ name: "FailedMessages");
+
+ migrationBuilder.DropTable(
+ name: "KnownEndpoints");
+
+ migrationBuilder.DropTable(
+ name: "KnownEndpointsInsertOnly");
+
+ migrationBuilder.DropTable(
+ name: "trial_metadata");
+ }
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
index 83b17187a4..5d94f7947b 100644
--- a/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/Migrations/PostgreSqlServiceControlDbContextModelSnapshot.cs
@@ -22,6 +22,50 @@ protected override void BuildModel(ModelBuilder modelBuilder)
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name");
+
+ b.Property("TrackInstances")
+ .HasColumnType("boolean")
+ .HasColumnName("track_instances");
+
+ b.HasKey("Name")
+ .HasName("pk_endpoint_settings");
+
+ b.ToTable("EndpointSettings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("EndpointAddress")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("endpoint_address");
+
+ b.Property("OriginalMessageIdentifier")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("original_message_identifier");
+
+ b.HasKey("Id")
+ .HasName("pk_failed_messages");
+
+ b.HasIndex("OriginalMessageIdentifier", "EndpointAddress")
+ .HasDatabaseName("ix_failed_messages_original_message_identifier_endpoint_address");
+
+ b.ToTable("FailedMessages", (string)null);
+ });
+
modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
{
b.Property("Id")
@@ -46,9 +90,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("text")
.HasColumnName("name");
- b.HasKey("Id");
+ b.HasKey("Id")
+ .HasName("pk_known_endpoints");
- b.ToTable("known_endpoints", (string)null);
+ b.ToTable("KnownEndpoints", (string)null);
});
modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointInsertOnlyEntity", b =>
@@ -78,11 +123,38 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("text")
.HasColumnName("name");
- b.HasKey("Id");
+ b.HasKey("Id")
+ .HasName("pk_known_endpoints_insert_only");
+
+ b.HasIndex("KnownEndpointId")
+ .HasDatabaseName("ix_known_endpoints_insert_only_known_endpoint_id");
+
+ b.ToTable("KnownEndpointsInsertOnly", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.TrialMetadataEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("TrialEndDate")
+ .HasColumnType("date")
+ .HasColumnName("trial_end_date");
+
+ b.HasKey("Id")
+ .HasName("pk_trial_metadata");
- b.HasIndex("KnownEndpointId");
+ b.ToTable("trial_metadata", (string)null);
- b.ToTable("known_endpoints_insert_only", (string)null);
+ b.HasData(
+ new
+ {
+ Id = 1
+ });
});
#pragma warning restore 612, 618
}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.Designer.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.Designer.cs
similarity index 57%
rename from src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.Designer.cs
rename to src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.Designer.cs
index 5901a2770e..d0129a6611 100644
--- a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.Designer.cs
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.Designer.cs
@@ -12,7 +12,7 @@
namespace ServiceControl.Persistence.EFCore.SqlServer.Migrations
{
[DbContext(typeof(SqlServerServiceControlDbContext))]
- [Migration("20260720230811_Initial")]
+ [Migration("20260722052648_Initial")]
partial class Initial
{
///
@@ -25,6 +25,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("TrackInstances")
+ .HasColumnType("bit");
+
+ b.HasKey("Name");
+
+ b.ToTable("EndpointSettings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EndpointAddress")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("OriginalMessageIdentifier")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OriginalMessageIdentifier", "EndpointAddress");
+
+ b.ToTable("FailedMessages", (string)null);
+ });
+
modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
{
b.Property("Id")
@@ -77,6 +113,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.ToTable("KnownEndpointsInsertOnly", (string)null);
});
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.TrialMetadataEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("TrialEndDate")
+ .HasColumnType("date");
+
+ b.HasKey("Id");
+
+ b.ToTable("TrialMetadata");
+
+ b.HasData(
+ new
+ {
+ Id = 1
+ });
+ });
#pragma warning restore 612, 618
}
}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.cs
similarity index 50%
rename from src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.cs
rename to src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.cs
index d1af2ee2e1..b95bbd57e7 100644
--- a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260720230811_Initial.cs
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/20260722052648_Initial.cs
@@ -11,6 +11,31 @@ public partial class Initial : Migration
///
protected override void Up(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.CreateTable(
+ name: "EndpointSettings",
+ columns: table => new
+ {
+ Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false),
+ TrackInstances = table.Column(type: "bit", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_EndpointSettings", x => x.Name);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "FailedMessages",
+ columns: table => new
+ {
+ Id = table.Column(type: "uniqueidentifier", nullable: false),
+ OriginalMessageIdentifier = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false),
+ EndpointAddress = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_FailedMessages", x => x.Id);
+ });
+
migrationBuilder.CreateTable(
name: "KnownEndpoints",
columns: table => new
@@ -42,6 +67,29 @@ protected override void Up(MigrationBuilder migrationBuilder)
table.PrimaryKey("PK_KnownEndpointsInsertOnly", x => x.Id);
});
+ migrationBuilder.CreateTable(
+ name: "TrialMetadata",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("SqlServer:Identity", "1, 1"),
+ TrialEndDate = table.Column(type: "date", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TrialMetadata", x => x.Id);
+ });
+
+ migrationBuilder.InsertData(
+ table: "TrialMetadata",
+ columns: new[] { "Id", "TrialEndDate" },
+ values: new object[] { 1, null });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FailedMessages_OriginalMessageIdentifier_EndpointAddress",
+ table: "FailedMessages",
+ columns: new[] { "OriginalMessageIdentifier", "EndpointAddress" });
+
migrationBuilder.CreateIndex(
name: "IX_KnownEndpointsInsertOnly_KnownEndpointId",
table: "KnownEndpointsInsertOnly",
@@ -51,11 +99,20 @@ protected override void Up(MigrationBuilder migrationBuilder)
///
protected override void Down(MigrationBuilder migrationBuilder)
{
+ migrationBuilder.DropTable(
+ name: "EndpointSettings");
+
+ migrationBuilder.DropTable(
+ name: "FailedMessages");
+
migrationBuilder.DropTable(
name: "KnownEndpoints");
migrationBuilder.DropTable(
name: "KnownEndpointsInsertOnly");
+
+ migrationBuilder.DropTable(
+ name: "TrialMetadata");
}
}
}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/SqlServerServiceControlDbContextModelSnapshot.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/SqlServerServiceControlDbContextModelSnapshot.cs
index df880e2f92..dd4216b82b 100644
--- a/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/SqlServerServiceControlDbContextModelSnapshot.cs
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/Migrations/SqlServerServiceControlDbContextModelSnapshot.cs
@@ -22,6 +22,42 @@ protected override void BuildModel(ModelBuilder modelBuilder)
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.EndpointSettingsEntity", b =>
+ {
+ b.Property("Name")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("TrackInstances")
+ .HasColumnType("bit");
+
+ b.HasKey("Name");
+
+ b.ToTable("EndpointSettings", (string)null);
+ });
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.FailedMessageEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EndpointAddress")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("OriginalMessageIdentifier")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OriginalMessageIdentifier", "EndpointAddress");
+
+ b.ToTable("FailedMessages", (string)null);
+ });
+
modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.KnownEndpointEntity", b =>
{
b.Property("Id")
@@ -74,6 +110,28 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("KnownEndpointsInsertOnly", (string)null);
});
+
+ modelBuilder.Entity("ServiceControl.Persistence.EFCore.Entities.TrialMetadataEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("TrialEndDate")
+ .HasColumnType("date");
+
+ b.HasKey("Id");
+
+ b.ToTable("TrialMetadata");
+
+ b.HasData(
+ new
+ {
+ Id = 1
+ });
+ });
#pragma warning restore 612, 618
}
}
diff --git a/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs b/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs
index 6a7e0a6fc9..c87c7adcf0 100644
--- a/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs
+++ b/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs
@@ -6,19 +6,23 @@ namespace ServiceControl.Persistence.EFCore.DbContexts;
public abstract class ServiceControlDbContext(DbContextOptions options) : DbContext(options)
{
+ public DbSet EndpointSettings { get; set; }
+ public DbSet FailedMessages { get; set; }
public DbSet KnownEndpoints { get; set; }
public DbSet KnownEndpointsInsertOnly { get; set; }
+ public DbSet TrialMetadata { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- optionsBuilder.EnableDetailedErrors();
- }
+ => optionsBuilder.EnableDetailedErrors();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
+ modelBuilder.ApplyConfiguration(new EndpointSettingsConfiguration());
+ modelBuilder.ApplyConfiguration(new FailedMessageEntityConfiguration());
modelBuilder.ApplyConfiguration(new KnownEndpointConfiguration());
modelBuilder.ApplyConfiguration(new KnownEndpointInsertOnlyConfiguration());
+ modelBuilder.ApplyConfiguration(new TrialMetadataEntityConfiguration());
}
}
diff --git a/src/ServiceControl.Persistence.EFCore/Entities/EndpointSettingsEntity.cs b/src/ServiceControl.Persistence.EFCore/Entities/EndpointSettingsEntity.cs
new file mode 100644
index 0000000000..2316c07245
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Entities/EndpointSettingsEntity.cs
@@ -0,0 +1,7 @@
+namespace ServiceControl.Persistence.EFCore.Entities;
+
+public class EndpointSettingsEntity
+{
+ public required string Name { get; set; }
+ public bool TrackInstances { get; set; }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/Entities/FailedMessageEntity.cs b/src/ServiceControl.Persistence.EFCore/Entities/FailedMessageEntity.cs
new file mode 100644
index 0000000000..2e4eca7cf0
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Entities/FailedMessageEntity.cs
@@ -0,0 +1,11 @@
+namespace ServiceControl.Persistence.EFCore.Entities;
+
+public class FailedMessageEntity
+{
+ //todo: incomplete, needs the ingestion done...
+ public Guid Id { get; set; }
+
+ public required string OriginalMessageIdentifier { get; set; }
+
+ public required string EndpointAddress { get; set; }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/Entities/TrialMetadataEntity.cs b/src/ServiceControl.Persistence.EFCore/Entities/TrialMetadataEntity.cs
new file mode 100644
index 0000000000..68299b4585
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Entities/TrialMetadataEntity.cs
@@ -0,0 +1,9 @@
+namespace ServiceControl.Persistence.EFCore.Entities;
+
+public class TrialMetadataEntity
+{
+ public const int TrialMetadataId = 1;
+
+ public int Id { get; set; }
+ public DateOnly? TrialEndDate { get; set; }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/EntityConfigurations/EndpointSettingsConfiguration.cs b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/EndpointSettingsConfiguration.cs
new file mode 100644
index 0000000000..bb81d61c48
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/EndpointSettingsConfiguration.cs
@@ -0,0 +1,15 @@
+namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
+
+using Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+public class EndpointSettingsConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("EndpointSettings");
+ builder.HasKey(x => x.Name);
+ builder.Property(x => x.Name).HasMaxLength(200).IsRequired();
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/EntityConfigurations/FailedMessageEntityConfiguration.cs b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/FailedMessageEntityConfiguration.cs
new file mode 100644
index 0000000000..225464d93c
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/FailedMessageEntityConfiguration.cs
@@ -0,0 +1,20 @@
+namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
+
+using Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+public class FailedMessageEntityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("FailedMessages");
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).ValueGeneratedNever();
+ //todo: validate column constraints
+ builder.Property(e => e.OriginalMessageIdentifier).HasMaxLength(200).IsRequired();
+ builder.Property(e => e.EndpointAddress).HasMaxLength(200).IsRequired();
+
+ builder.HasIndex(e => new { e.OriginalMessageIdentifier, e.EndpointAddress });
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/EntityConfigurations/TrialMetadataEntityConfiguration.cs b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/TrialMetadataEntityConfiguration.cs
new file mode 100644
index 0000000000..40ca0a0fef
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/EntityConfigurations/TrialMetadataEntityConfiguration.cs
@@ -0,0 +1,18 @@
+namespace ServiceControl.Persistence.EFCore.EntityConfigurations;
+
+using Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+public class TrialMetadataEntityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.HasKey(e => e.Id);
+ builder.HasData(new TrialMetadataEntity
+ {
+ Id = 1,
+ TrialEndDate = null
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/DataStoreBase.cs b/src/ServiceControl.Persistence.EFCore/Implementation/DataStoreBase.cs
index 157450c54b..90299c070e 100644
--- a/src/ServiceControl.Persistence.EFCore/Implementation/DataStoreBase.cs
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/DataStoreBase.cs
@@ -1,6 +1,7 @@
namespace ServiceControl.Persistence.EFCore.Implementation;
using System;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DbContexts;
using Microsoft.Extensions.DependencyInjection;
@@ -32,6 +33,19 @@ protected async Task ExecuteWithDbContext(Func op
await operation(dbContext);
}
+ ///
+ /// Executes an operation with a scoped DbContext, without returning a result
+ ///
+ protected async IAsyncEnumerable ExecuteWithDbContext(Func> operation, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ await using var scope = scopeFactory.CreateAsyncScope();
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+ await foreach (var row in operation(dbContext).WithCancellation(cancellationToken))
+ {
+ yield return row;
+ }
+ }
+
///
/// Creates a scope for operations that need to manage their own scope lifecycle (e.g., managers)
///
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs
index 6de13c0fa4..cb773ea3ca 100644
--- a/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs
@@ -1,13 +1,45 @@
namespace ServiceControl.Persistence.EFCore.Implementation;
-public class EndpointSettingsStore : IEndpointSettingsStore
+using Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+public class EndpointSettingsStore(IServiceScopeFactory scopeFactory) : DataStoreBase(scopeFactory), IEndpointSettingsStore
{
- public IAsyncEnumerable GetAllEndpointSettings(CancellationToken cancellationToken) =>
- throw new NotImplementedException();
+ public IAsyncEnumerable GetAllEndpointSettings(CancellationToken cancellationToken)
+ => ExecuteWithDbContext(context => context.EndpointSettings.Select(row => new EndpointSettings
+ {
+ Name = row.Name,
+ TrackInstances = row.TrackInstances
+ }).AsAsyncEnumerable(), cancellationToken);
+
+ public Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken token) => ExecuteWithDbContext(async context =>
+ {
+ var entity = context.EndpointSettings.Find(settings.Name);
+ if (entity == null)
+ {
+ entity = new EndpointSettingsEntity() { Name = settings.Name, TrackInstances = settings.TrackInstances };
+ context.EndpointSettings.Add(entity);
+ try
+ {
+ await context.SaveChangesAsync(token);
+ return;
+ }
+ catch (DbUpdateException)
+ {
+ //this probably failed because of key conflict so try again
+ }
+
+ await context.Entry(entity).ReloadAsync(token);
+ }
- public Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken token) =>
- throw new NotImplementedException();
+ entity.TrackInstances = settings.TrackInstances;
+ await context.SaveChangesAsync(token);
+ });
- public Task Delete(string name, CancellationToken cancellationToken) =>
- throw new NotImplementedException();
+ public Task Delete(string name, CancellationToken cancellationToken) => ExecuteWithDbContext(async context =>
+ {
+ context.EndpointSettings.RemoveRange(context.EndpointSettings.Where(x => x.Name == name));
+ await context.SaveChangesAsync(cancellationToken);
+ });
}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/QueueAddressStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/QueueAddressStore.cs
index b53673b630..9dafbf3509 100644
--- a/src/ServiceControl.Persistence.EFCore/Implementation/QueueAddressStore.cs
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/QueueAddressStore.cs
@@ -1,13 +1,32 @@
namespace ServiceControl.Persistence.EFCore.Implementation;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
using ServiceControl.MessageFailures;
using ServiceControl.Persistence.Infrastructure;
-public class QueueAddressStore : IQueueAddressStore
+public class QueueAddressStore(IServiceScopeFactory scopeFactory) : DataStoreBase(scopeFactory), IQueueAddressStore
{
public Task>> GetAddresses(PagingInfo pagingInfo) =>
- throw new NotImplementedException();
+ GetAddressesBySearchTerm(string.Empty, pagingInfo);
public Task>> GetAddressesBySearchTerm(string search, PagingInfo pagingInfo) =>
- throw new NotImplementedException();
-}
+ ExecuteWithDbContext(async context =>
+ {
+ var query = context.FailedMessages
+ .Where(fm => string.IsNullOrWhiteSpace(search) || fm.EndpointAddress.StartsWith(search))
+ .Select(fm => new { fm.OriginalMessageIdentifier, fm.EndpointAddress })
+ .Distinct()
+ .GroupBy(failure => failure.EndpointAddress)
+ .OrderBy(failuresByEndpoint => failuresByEndpoint.Key)
+ .Select(failuresByEndpoint => new QueueAddress
+ {
+ PhysicalAddress = failuresByEndpoint.Key,
+ FailedMessageCount = failuresByEndpoint.Count()
+ });
+
+ var items = await query.Skip(pagingInfo.Offset).Take(pagingInfo.PageSize).ToListAsync();
+
+ return new QueryResult>(items, new QueryStatsInfo("", query.Count(), false));
+ });
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/TrialLicenseDataProvider.cs b/src/ServiceControl.Persistence.EFCore/Implementation/TrialLicenseDataProvider.cs
index e10739ee89..44a317d0bc 100644
--- a/src/ServiceControl.Persistence.EFCore/Implementation/TrialLicenseDataProvider.cs
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/TrialLicenseDataProvider.cs
@@ -1,10 +1,23 @@
namespace ServiceControl.Persistence.EFCore.Implementation;
-public class TrialLicenseDataProvider : ITrialLicenseDataProvider
+using Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+public class TrialLicenseDataProvider(IServiceScopeFactory scopeFactory) : DataStoreBase(scopeFactory), ITrialLicenseDataProvider
{
- public Task GetTrialEndDate(CancellationToken cancellationToken) =>
- throw new NotImplementedException();
+ public Task GetTrialEndDate(CancellationToken cancellationToken)
+ => ExecuteWithDbContext(async context =>
+ {
+ var trialMetadata = await context.TrialMetadata.SingleAsync(t => t.Id == TrialMetadataEntity.TrialMetadataId, cancellationToken);
+ return trialMetadata.TrialEndDate;
+ });
- public Task StoreTrialEndDate(DateOnly trialEndDate, CancellationToken cancellationToken) =>
- throw new NotImplementedException();
-}
+ public Task StoreTrialEndDate(DateOnly trialEndDate, CancellationToken cancellationToken)
+ => ExecuteWithDbContext(async context =>
+ {
+ var trialMetadata = await context.TrialMetadata.SingleAsync(t => t.Id == TrialMetadataEntity.TrialMetadataId, cancellationToken);
+ trialMetadata.TrialEndDate = trialEndDate;
+ await (Task)context.SaveChangesAsync(cancellationToken);
+ });
+}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.Tests.PostgreSql/PersistenceTestsContext.cs b/src/ServiceControl.Persistence.Tests.PostgreSql/PersistenceTestsContext.cs
index a104f188eb..cc05843a14 100644
--- a/src/ServiceControl.Persistence.Tests.PostgreSql/PersistenceTestsContext.cs
+++ b/src/ServiceControl.Persistence.Tests.PostgreSql/PersistenceTestsContext.cs
@@ -10,6 +10,7 @@ namespace ServiceControl.Persistence.Tests;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Time.Testing;
using Npgsql;
+using ServiceControl.Persistence.EFCore.Entities;
using ServiceControl.Persistence.EFCore.Infrastructure;
public class PersistenceTestsContext : IPersistenceTestsContext
@@ -69,6 +70,21 @@ public async Task CompleteDatabaseOperation()
}
}
+ public async Task SeedFailedMessagesForQueueAddressStore(params (string OriginalMessageIdentifier, string EndpointAddress)[] failedMessages)
+ {
+ using var scope = host.Services.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+
+ db.FailedMessages.AddRange(failedMessages.Select(failedMessage => new FailedMessageEntity
+ {
+ Id = Guid.NewGuid(),
+ OriginalMessageIdentifier = failedMessage.OriginalMessageIdentifier,
+ EndpointAddress = failedMessage.EndpointAddress
+ }));
+
+ await db.SaveChangesAsync();
+ }
+
public PersistenceSettings PersistenceSettings { get; set; }
public string GenerateFailedMessageRecordId(string messageId) => messageId;
diff --git a/src/ServiceControl.Persistence.Tests.RavenDB/PersistenceTestsContext.cs b/src/ServiceControl.Persistence.Tests.RavenDB/PersistenceTestsContext.cs
index 63e455105c..fc2c3b4bc6 100644
--- a/src/ServiceControl.Persistence.Tests.RavenDB/PersistenceTestsContext.cs
+++ b/src/ServiceControl.Persistence.Tests.RavenDB/PersistenceTestsContext.cs
@@ -8,6 +8,8 @@ namespace ServiceControl.Persistence.Tests;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;
using Raven.Client.Documents;
+using ServiceControl.Contracts.Operations;
+using ServiceControl.MessageFailures;
using ServiceControl.Persistence;
using ServiceControl.Persistence.RavenDB;
using ServiceControl.RavenDB;
@@ -65,6 +67,37 @@ public Task CompleteDatabaseOperation()
return Task.CompletedTask;
}
+ public async Task SeedFailedMessagesForQueueAddressStore(params (string OriginalMessageIdentifier, string EndpointAddress)[] failedMessages)
+ {
+ using var session = await SessionProvider.OpenSession();
+
+ foreach (var (originalMessageIdentifier, endpointAddress) in failedMessages)
+ {
+ var documentId = $"FailedMessages/{originalMessageIdentifier}";
+ var failedMessage = new FailedMessage
+ {
+ Id = documentId,
+ UniqueMessageId = originalMessageIdentifier,
+ Status = FailedMessageStatus.Unresolved,
+ ProcessingAttempts =
+ [
+ new FailedMessage.ProcessingAttempt
+ {
+ AttemptedAt = DateTime.UtcNow,
+ FailureDetails = new FailureDetails
+ {
+ AddressOfFailingEndpoint = endpointAddress
+ }
+ }
+ ]
+ };
+
+ await session.StoreAsync(failedMessage, documentId);
+ }
+
+ await session.SaveChangesAsync();
+ }
+
[Conditional("DEBUG")]
public void BlockToInspectDatabase()
{
diff --git a/src/ServiceControl.Persistence.Tests.SqlServer/PersistenceTestsContext.cs b/src/ServiceControl.Persistence.Tests.SqlServer/PersistenceTestsContext.cs
index 9af5dc6de0..ad6b2199c6 100644
--- a/src/ServiceControl.Persistence.Tests.SqlServer/PersistenceTestsContext.cs
+++ b/src/ServiceControl.Persistence.Tests.SqlServer/PersistenceTestsContext.cs
@@ -10,6 +10,8 @@ namespace ServiceControl.Persistence.Tests;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Time.Testing;
+using ServiceControl.Persistence.EFCore.DbContexts;
+using ServiceControl.Persistence.EFCore.Entities;
using ServiceControl.Persistence.EFCore.Infrastructure;
public class PersistenceTestsContext : IPersistenceTestsContext
@@ -75,6 +77,21 @@ public async Task CompleteDatabaseOperation()
}
}
+ public async Task SeedFailedMessagesForQueueAddressStore(params (string OriginalMessageIdentifier, string EndpointAddress)[] failedMessages)
+ {
+ using var scope = host.Services.CreateScope();
+ var db = scope.ServiceProvider.GetRequiredService();
+
+ db.FailedMessages.AddRange(failedMessages.Select(failedMessage => new FailedMessageEntity
+ {
+ Id = Guid.NewGuid(),
+ OriginalMessageIdentifier = failedMessage.OriginalMessageIdentifier,
+ EndpointAddress = failedMessage.EndpointAddress
+ }));
+
+ await db.SaveChangesAsync();
+ }
+
public PersistenceSettings PersistenceSettings { get; set; }
public string GenerateFailedMessageRecordId(string messageId) => messageId;
diff --git a/src/ServiceControl.Persistence.Tests/EndpointSettingsStoreTests.cs b/src/ServiceControl.Persistence.Tests/EndpointSettingsStoreTests.cs
new file mode 100644
index 0000000000..7705f1b9a8
--- /dev/null
+++ b/src/ServiceControl.Persistence.Tests/EndpointSettingsStoreTests.cs
@@ -0,0 +1,55 @@
+namespace ServiceControl.Persistence.Tests;
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using ServiceControl.Persistence;
+
+class EndpointSettingsStoreTests : PersistenceTestBase
+{
+ [Test]
+ public async Task UpdateEndpointSettings_stores_and_updates_existing_setting()
+ {
+ await EndpointSettingsStore.UpdateEndpointSettings(new EndpointSettings { Name = "Sales", TrackInstances = false }, default);
+ await EndpointSettingsStore.UpdateEndpointSettings(new EndpointSettings { Name = "Sales", TrackInstances = true }, default);
+
+ var settings = await GetAllEndpointSettings();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(settings, Has.Count.EqualTo(1));
+ Assert.That(settings.Single().Name, Is.EqualTo("Sales"));
+ Assert.That(settings.Single().TrackInstances, Is.True);
+ }
+ }
+
+ [Test]
+ public async Task Delete_removes_only_target_setting()
+ {
+ await EndpointSettingsStore.UpdateEndpointSettings(new EndpointSettings { Name = "Sales", TrackInstances = false }, default);
+ await EndpointSettingsStore.UpdateEndpointSettings(new EndpointSettings { Name = "Shipping", TrackInstances = true }, default);
+
+ await EndpointSettingsStore.Delete("Sales", default);
+
+ var settings = await GetAllEndpointSettings();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(settings, Has.Count.EqualTo(1));
+ Assert.That(settings.Single().Name, Is.EqualTo("Shipping"));
+ Assert.That(settings.Single().TrackInstances, Is.True);
+ }
+ }
+
+ async Task> GetAllEndpointSettings()
+ {
+ var settings = new List();
+ await foreach (var setting in EndpointSettingsStore.GetAllEndpointSettings())
+ {
+ settings.Add(setting);
+ }
+
+ return settings;
+ }
+}
diff --git a/src/ServiceControl.Persistence.Tests/IPersistenceTestsContext.cs b/src/ServiceControl.Persistence.Tests/IPersistenceTestsContext.cs
index bf59a57835..b52c419ec5 100644
--- a/src/ServiceControl.Persistence.Tests/IPersistenceTestsContext.cs
+++ b/src/ServiceControl.Persistence.Tests/IPersistenceTestsContext.cs
@@ -13,6 +13,8 @@ public interface IPersistenceTestsContext
Task CompleteDatabaseOperation();
+ Task SeedFailedMessagesForQueueAddressStore(params (string OriginalMessageIdentifier, string EndpointAddress)[] failedMessages);
+
PersistenceSettings PersistenceSettings { get; }
string GenerateFailedMessageRecordId(string messageId);
diff --git a/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs b/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs
index e4175e2c4a..dff4fbd232 100644
--- a/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs
+++ b/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs
@@ -107,4 +107,6 @@ protected static async Task WaitUntil(Func> conditionChecker, string
protected IEventLogDataStore EventLogDataStore => ServiceProvider.GetRequiredService();
protected IRetryDocumentDataStore RetryDocumentDataStore => ServiceProvider.GetRequiredService();
protected ILicensingDataStore LicensingDataStore => ServiceProvider.GetRequiredService();
+ protected IQueueAddressStore QueueAddressStore => ServiceProvider.GetRequiredService();
+ protected IEndpointSettingsStore EndpointSettingsStore => ServiceProvider.GetRequiredService();
}
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.Tests/QueueAddressStoreTests.cs b/src/ServiceControl.Persistence.Tests/QueueAddressStoreTests.cs
new file mode 100644
index 0000000000..7031626717
--- /dev/null
+++ b/src/ServiceControl.Persistence.Tests/QueueAddressStoreTests.cs
@@ -0,0 +1,60 @@
+namespace ServiceControl.Persistence.Tests;
+
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using ServiceControl.Persistence.Infrastructure;
+
+class QueueAddressStoreTests : PersistenceTestBase
+{
+ [Test]
+ public async Task GetAddresses_groups_failed_messages_by_queue_address()
+ {
+ await SeedFailedMessages();
+
+ await CompleteDatabaseOperation();
+
+ var result = await QueueAddressStore.GetAddresses(new PagingInfo(1, 10));
+ var addresses = result.Results;
+ var physicalAddresses = addresses.Select(address => address.PhysicalAddress).OrderBy(address => address).ToArray();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(result.QueryStats.TotalCount, Is.EqualTo(4));
+ Assert.That(physicalAddresses, Is.EqualTo(new[] { "alpha", "alpha-child", "beta", "gamma" }));
+ Assert.That(addresses.Single(address => address.PhysicalAddress == "alpha").FailedMessageCount, Is.EqualTo(2));
+ Assert.That(addresses.Single(address => address.PhysicalAddress == "alpha-child").FailedMessageCount, Is.EqualTo(1));
+ Assert.That(addresses.Single(address => address.PhysicalAddress == "beta").FailedMessageCount, Is.EqualTo(1));
+ Assert.That(addresses.Single(address => address.PhysicalAddress == "gamma").FailedMessageCount, Is.EqualTo(1));
+ }
+ }
+
+ [Test]
+ public async Task GetAddressesBySearchTerm_filters_by_prefix()
+ {
+ await SeedFailedMessages();
+
+ await CompleteDatabaseOperation();
+
+ var result = await QueueAddressStore.GetAddressesBySearchTerm("alpha", new PagingInfo(1, 10));
+ var addresses = result.Results;
+ var physicalAddresses = addresses.Select(address => address.PhysicalAddress).OrderBy(address => address).ToArray();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(result.QueryStats.TotalCount, Is.EqualTo(2));
+ Assert.That(physicalAddresses, Is.EqualTo(new[] { "alpha", "alpha-child" }));
+ Assert.That(addresses.Sum(address => address.FailedMessageCount), Is.EqualTo(3));
+ }
+ }
+
+ async Task SeedFailedMessages()
+ {
+ await PersistenceTestsContext.SeedFailedMessagesForQueueAddressStore(
+ ("msg-1", "alpha"),
+ ("msg-2", "alpha"),
+ ("msg-3", "beta"),
+ ("msg-4", "alpha-child"),
+ ("msg-5", "gamma"));
+ }
+}
diff --git a/src/ServiceControl.Persistence.Tests/TrialLicenseDataProviderTests.cs b/src/ServiceControl.Persistence.Tests/TrialLicenseDataProviderTests.cs
new file mode 100644
index 0000000000..627ea19be2
--- /dev/null
+++ b/src/ServiceControl.Persistence.Tests/TrialLicenseDataProviderTests.cs
@@ -0,0 +1,33 @@
+namespace ServiceControl.Persistence.Tests;
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using NUnit.Framework;
+using ServiceControl.Persistence;
+
+class TrialLicenseDataProviderTests : PersistenceTestBase
+{
+ [Test]
+ public async Task GetTrialEndDate_returns_null_by_default()
+ {
+ var trialLicenseDataProvider = ServiceProvider.GetRequiredService();
+
+ var trialEndDate = await trialLicenseDataProvider.GetTrialEndDate(default);
+
+ Assert.That(trialEndDate, Is.Null);
+ }
+
+ [Test]
+ public async Task StoreTrialEndDate_persists_value()
+ {
+ var trialLicenseDataProvider = ServiceProvider.GetRequiredService();
+ var expectedEndDate = DateOnly.FromDateTime(DateTime.UtcNow.Date.AddDays(13));
+
+ await trialLicenseDataProvider.StoreTrialEndDate(expectedEndDate, default);
+
+ var trialEndDate = await trialLicenseDataProvider.GetTrialEndDate(default);
+
+ Assert.That(trialEndDate, Is.EqualTo(expectedEndDate));
+ }
+}
diff --git a/src/ServiceControl.Persistence/IEndpointSettingsStore.cs b/src/ServiceControl.Persistence/IEndpointSettingsStore.cs
index d41296dd3a..2b6c52e8a1 100644
--- a/src/ServiceControl.Persistence/IEndpointSettingsStore.cs
+++ b/src/ServiceControl.Persistence/IEndpointSettingsStore.cs
@@ -6,7 +6,7 @@
public interface IEndpointSettingsStore
{
- IAsyncEnumerable GetAllEndpointSettings(CancellationToken token);
+ IAsyncEnumerable GetAllEndpointSettings(CancellationToken cancellationToken = default);
Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken token);
Task Delete(string name, CancellationToken cancellationToken);