diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index c353458ebe..e0e28c4942 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -30,6 +30,12 @@
+
+
+
+
+
+
@@ -92,6 +98,8 @@
+
+
diff --git a/src/ProjectReferences.Persisters.Primary.props b/src/ProjectReferences.Persisters.Primary.props
index 255b45ed5c..de0fbcd871 100644
--- a/src/ProjectReferences.Persisters.Primary.props
+++ b/src/ProjectReferences.Persisters.Primary.props
@@ -1,6 +1,8 @@
+
+
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/.editorconfig b/src/ServiceControl.Persistence.EFCore.PostgreSql/.editorconfig
new file mode 100644
index 0000000000..1a80b3d27b
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/.editorconfig
@@ -0,0 +1,9 @@
+[*.cs]
+
+# Justification: ServiceControl app has no synchronization context
+dotnet_diagnostic.CA2007.severity = none
+
+# Disable style rules for auto-generated EF migrations
+[Migrations/**.cs]
+dotnet_diagnostic.IDE0065.severity = none
+generated_code = true
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistence.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistence.cs
new file mode 100644
index 0000000000..0f3f01ad9a
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistence.cs
@@ -0,0 +1,32 @@
+namespace ServiceControl.Persistence.EFCore.PostgreSql;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+class PostgreSqlPersistence(PostgreSqlPersisterSettings settings) : BasePersistence, IPersistence
+{
+ public void AddPersistence(IServiceCollection services)
+ {
+ RegisterSettings(services);
+ ConfigureDbContext(services);
+ RegisterDataStores(services);
+ }
+
+ public void AddInstaller(IServiceCollection services)
+ {
+ RegisterSettings(services);
+ ConfigureDbContext(services);
+ }
+
+ void RegisterSettings(IServiceCollection services)
+ {
+ services.AddSingleton(settings);
+ services.AddSingleton(settings);
+ services.AddSingleton(settings);
+ }
+
+ void ConfigureDbContext(IServiceCollection services) =>
+ services.AddPooledDbContextFactory(options =>
+ options.UseNpgsql(settings.ConnectionString, npgsql => npgsql.CommandTimeout(settings.CommandTimeout)));
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistenceConfiguration.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistenceConfiguration.cs
new file mode 100644
index 0000000000..d6e8705dd3
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistenceConfiguration.cs
@@ -0,0 +1,20 @@
+namespace ServiceControl.Persistence.EFCore.PostgreSql;
+
+using Microsoft.Extensions.Logging;
+using ServiceControl.Infrastructure;
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+class PostgreSqlPersistenceConfiguration : EFPersistenceConfigurationBase
+{
+ public override IPersistence Create(PersistenceSettings settings)
+ {
+ // Temporary until the persister is fully implemented
+ LoggerUtil.CreateStaticLogger()
+ .LogError("The PostgreSQL persistence is still under development and is not ready for use");
+
+ return new PostgreSqlPersistence((PostgreSqlPersisterSettings)settings);
+ }
+
+ protected override EFPersisterSettings CreateSettings(string connectionString) =>
+ new PostgreSqlPersisterSettings { ConnectionString = connectionString };
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersisterSettings.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersisterSettings.cs
new file mode 100644
index 0000000000..88cba1ef04
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersisterSettings.cs
@@ -0,0 +1,7 @@
+namespace ServiceControl.Persistence.EFCore.PostgreSql;
+
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+public class PostgreSqlPersisterSettings : EFPersisterSettings
+{
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContext.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContext.cs
new file mode 100644
index 0000000000..3f7baa435e
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContext.cs
@@ -0,0 +1,8 @@
+namespace ServiceControl.Persistence.EFCore.PostgreSql;
+
+using Microsoft.EntityFrameworkCore;
+using ServiceControl.Persistence.EFCore.DbContexts;
+
+public class PostgreSqlServiceControlDbContext(DbContextOptions options) : ServiceControlDbContext(options)
+{
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContextFactory.cs b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContextFactory.cs
new file mode 100644
index 0000000000..5a17acb93f
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlServiceControlDbContextFactory.cs
@@ -0,0 +1,21 @@
+namespace ServiceControl.Persistence.EFCore.PostgreSql;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+///
+/// Design-time factory used by the dotnet-ef tooling (e.g. dotnet ef migrations add).
+///
+public class PostgreSqlServiceControlDbContextFactory : IDesignTimeDbContextFactory
+{
+ public PostgreSqlServiceControlDbContext CreateDbContext(string[] args)
+ {
+ var connectionString = Environment.GetEnvironmentVariable("SERVICECONTROL_DATABASE_CONNECTIONSTRING")
+ ?? "Host=localhost;Port=5432;Database=servicecontrol;Username=postgres;Password=postgres";
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(connectionString);
+
+ return new PostgreSqlServiceControlDbContext(optionsBuilder.Options);
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/ServiceControl.Persistence.EFCore.PostgreSql.csproj b/src/ServiceControl.Persistence.EFCore.PostgreSql/ServiceControl.Persistence.EFCore.PostgreSql.csproj
new file mode 100644
index 0000000000..f638c069d0
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/ServiceControl.Persistence.EFCore.PostgreSql.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net10.0
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ServiceControl.Persistence.EFCore.PostgreSql/persistence.manifest b/src/ServiceControl.Persistence.EFCore.PostgreSql/persistence.manifest
new file mode 100644
index 0000000000..925712ab78
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.PostgreSql/persistence.manifest
@@ -0,0 +1,25 @@
+{
+ "Name": "PostgreSQL",
+ "DisplayName": "PostgreSQL",
+ "Description": "PostgreSQL ServiceControl persister",
+ "AssemblyName": "ServiceControl.Persistence.EFCore.PostgreSql",
+ "TypeName": "ServiceControl.Persistence.EFCore.PostgreSql.PostgreSqlPersistenceConfiguration, ServiceControl.Persistence.EFCore.PostgreSql",
+ "Settings": [
+ {
+ "Name": "ServiceControl/Database/ConnectionString",
+ "Mandatory": true
+ },
+ {
+ "Name": "ServiceControl/Database/CommandTimeout",
+ "Mandatory": false
+ },
+ {
+ "Name": "ServiceControl/MessageBody/StoragePath",
+ "Mandatory": false
+ },
+ {
+ "Name": "ServiceControl/MessageBody/MinCompressionSize",
+ "Mandatory": false
+ }
+ ]
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/.editorconfig b/src/ServiceControl.Persistence.EFCore.SqlServer/.editorconfig
new file mode 100644
index 0000000000..1a80b3d27b
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/.editorconfig
@@ -0,0 +1,9 @@
+[*.cs]
+
+# Justification: ServiceControl app has no synchronization context
+dotnet_diagnostic.CA2007.severity = none
+
+# Disable style rules for auto-generated EF migrations
+[Migrations/**.cs]
+dotnet_diagnostic.IDE0065.severity = none
+generated_code = true
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/ServiceControl.Persistence.EFCore.SqlServer.csproj b/src/ServiceControl.Persistence.EFCore.SqlServer/ServiceControl.Persistence.EFCore.SqlServer.csproj
new file mode 100644
index 0000000000..862beed816
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/ServiceControl.Persistence.EFCore.SqlServer.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net10.0
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistence.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistence.cs
new file mode 100644
index 0000000000..afbf49c124
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistence.cs
@@ -0,0 +1,32 @@
+namespace ServiceControl.Persistence.EFCore.SqlServer;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+class SqlServerPersistence(SqlServerPersisterSettings settings) : BasePersistence, IPersistence
+{
+ public void AddPersistence(IServiceCollection services)
+ {
+ RegisterSettings(services);
+ ConfigureDbContext(services);
+ RegisterDataStores(services);
+ }
+
+ public void AddInstaller(IServiceCollection services)
+ {
+ RegisterSettings(services);
+ ConfigureDbContext(services);
+ }
+
+ void RegisterSettings(IServiceCollection services)
+ {
+ services.AddSingleton(settings);
+ services.AddSingleton(settings);
+ services.AddSingleton(settings);
+ }
+
+ void ConfigureDbContext(IServiceCollection services) =>
+ services.AddPooledDbContextFactory(options =>
+ options.UseSqlServer(settings.ConnectionString, sqlServer => sqlServer.CommandTimeout(settings.CommandTimeout)));
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistenceConfiguration.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistenceConfiguration.cs
new file mode 100644
index 0000000000..49d5d8e415
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistenceConfiguration.cs
@@ -0,0 +1,20 @@
+namespace ServiceControl.Persistence.EFCore.SqlServer;
+
+using Microsoft.Extensions.Logging;
+using ServiceControl.Infrastructure;
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+class SqlServerPersistenceConfiguration : EFPersistenceConfigurationBase
+{
+ public override IPersistence Create(PersistenceSettings settings)
+ {
+ // Temporary until the persister is fully implemented
+ LoggerUtil.CreateStaticLogger()
+ .LogError("The SQL Server persistence is still under development and is not ready for use");
+
+ return new SqlServerPersistence((SqlServerPersisterSettings)settings);
+ }
+
+ protected override EFPersisterSettings CreateSettings(string connectionString) =>
+ new SqlServerPersisterSettings { ConnectionString = connectionString };
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersisterSettings.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersisterSettings.cs
new file mode 100644
index 0000000000..9f3bd871b4
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersisterSettings.cs
@@ -0,0 +1,7 @@
+namespace ServiceControl.Persistence.EFCore.SqlServer;
+
+using ServiceControl.Persistence.EFCore.Abstractions;
+
+public class SqlServerPersisterSettings : EFPersisterSettings
+{
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContext.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContext.cs
new file mode 100644
index 0000000000..528ab19b52
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContext.cs
@@ -0,0 +1,8 @@
+namespace ServiceControl.Persistence.EFCore.SqlServer;
+
+using Microsoft.EntityFrameworkCore;
+using ServiceControl.Persistence.EFCore.DbContexts;
+
+public class SqlServerServiceControlDbContext(DbContextOptions options) : ServiceControlDbContext(options)
+{
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContextFactory.cs b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContextFactory.cs
new file mode 100644
index 0000000000..2be9b44199
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerServiceControlDbContextFactory.cs
@@ -0,0 +1,21 @@
+namespace ServiceControl.Persistence.EFCore.SqlServer;
+
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+///
+/// Design-time factory used by the dotnet-ef tooling (e.g. dotnet ef migrations add).
+///
+public class SqlServerServiceControlDbContextFactory : IDesignTimeDbContextFactory
+{
+ public SqlServerServiceControlDbContext CreateDbContext(string[] args)
+ {
+ var connectionString = Environment.GetEnvironmentVariable("SERVICECONTROL_DATABASE_CONNECTIONSTRING")
+ ?? "Server=localhost;Database=ServiceControl;Trusted_Connection=True;TrustServerCertificate=True";
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseSqlServer(connectionString);
+
+ return new SqlServerServiceControlDbContext(optionsBuilder.Options);
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore.SqlServer/persistence.manifest b/src/ServiceControl.Persistence.EFCore.SqlServer/persistence.manifest
new file mode 100644
index 0000000000..aa1b57b88f
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore.SqlServer/persistence.manifest
@@ -0,0 +1,25 @@
+{
+ "Name": "SQLServer",
+ "DisplayName": "SQL Server",
+ "Description": "SQL Server ServiceControl persister",
+ "AssemblyName": "ServiceControl.Persistence.EFCore.SqlServer",
+ "TypeName": "ServiceControl.Persistence.EFCore.SqlServer.SqlServerPersistenceConfiguration, ServiceControl.Persistence.EFCore.SqlServer",
+ "Settings": [
+ {
+ "Name": "ServiceControl/Database/ConnectionString",
+ "Mandatory": true
+ },
+ {
+ "Name": "ServiceControl/Database/CommandTimeout",
+ "Mandatory": false
+ },
+ {
+ "Name": "ServiceControl/MessageBody/StoragePath",
+ "Mandatory": false
+ },
+ {
+ "Name": "ServiceControl/MessageBody/MinCompressionSize",
+ "Mandatory": false
+ }
+ ]
+}
diff --git a/src/ServiceControl.Persistence.EFCore/.editorconfig b/src/ServiceControl.Persistence.EFCore/.editorconfig
new file mode 100644
index 0000000000..1a80b3d27b
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/.editorconfig
@@ -0,0 +1,9 @@
+[*.cs]
+
+# Justification: ServiceControl app has no synchronization context
+dotnet_diagnostic.CA2007.severity = none
+
+# Disable style rules for auto-generated EF migrations
+[Migrations/**.cs]
+dotnet_diagnostic.IDE0065.severity = none
+generated_code = true
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.EFCore/Abstractions/BasePersistence.cs b/src/ServiceControl.Persistence.EFCore/Abstractions/BasePersistence.cs
new file mode 100644
index 0000000000..0076c9ae81
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Abstractions/BasePersistence.cs
@@ -0,0 +1,50 @@
+namespace ServiceControl.Persistence.EFCore.Abstractions;
+
+using Microsoft.Extensions.DependencyInjection;
+using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions;
+using Particular.LicensingComponent.Persistence;
+using ServiceControl.Operations.BodyStorage;
+using ServiceControl.Persistence.EFCore.Implementation;
+using ServiceControl.Persistence.EFCore.Implementation.UnitOfWork;
+using ServiceControl.Persistence.MessageRedirects;
+using ServiceControl.Persistence.Recoverability;
+using ServiceControl.Persistence.UnitOfWork;
+
+public abstract class BasePersistence
+{
+ protected static void RegisterDataStores(IServiceCollection services)
+ {
+ services.AddSingleton(TimeProvider.System);
+
+ services.AddSingleton();
+ services.AddSingleton(p => p.GetRequiredService());
+
+ services.AddUnitOfWorkFactory();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton(p => p.GetRequiredService());
+ services.AddHostedService(p => p.GetRequiredService());
+
+ services.AddSingleton();
+ services.AddSingleton(p => p.GetRequiredService());
+ services.AddHostedService(p => p.GetRequiredService());
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersistenceConfigurationBase.cs b/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersistenceConfigurationBase.cs
new file mode 100644
index 0000000000..54f272d30c
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersistenceConfigurationBase.cs
@@ -0,0 +1,40 @@
+namespace ServiceControl.Persistence.EFCore.Abstractions;
+
+using ServiceControl.Configuration;
+
+public abstract class EFPersistenceConfigurationBase : IPersistenceConfiguration
+{
+ const string ConnectionStringKey = "Database/ConnectionString";
+ const string CommandTimeoutKey = "Database/CommandTimeout";
+ const string MessageBodyStoragePathKey = "MessageBody/StoragePath";
+ const string MinBodySizeForCompressionKey = "MessageBody/MinCompressionSize";
+ const string ErrorRetentionPeriodKey = "ErrorRetentionPeriod";
+ const string EnableFullTextSearchOnBodiesKey = "EnableFullTextSearchOnBodies";
+
+ public PersistenceSettings CreateSettings(SettingsRootNamespace settingsRootNamespace)
+ {
+ var settings = CreateSettings(GetRequiredSetting(settingsRootNamespace, ConnectionStringKey));
+
+ settings.CommandTimeout = SettingsReader.Read(settingsRootNamespace, CommandTimeoutKey, 30);
+ settings.MessageBodyStoragePath = SettingsReader.Read(settingsRootNamespace, MessageBodyStoragePathKey);
+ settings.MinBodySizeForCompression = SettingsReader.Read(settingsRootNamespace, MinBodySizeForCompressionKey, 4096);
+ settings.ErrorRetentionPeriod = GetRequiredSetting(settingsRootNamespace, ErrorRetentionPeriodKey);
+ settings.EnableFullTextSearchOnBodies = SettingsReader.Read(settingsRootNamespace, EnableFullTextSearchOnBodiesKey, true);
+
+ return settings;
+ }
+
+ public abstract IPersistence Create(PersistenceSettings settings);
+
+ protected abstract EFPersisterSettings CreateSettings(string connectionString);
+
+ static T GetRequiredSetting(SettingsRootNamespace settingsRootNamespace, string key)
+ {
+ if (SettingsReader.TryRead(settingsRootNamespace, key, out var value))
+ {
+ return value;
+ }
+
+ throw new Exception($"Setting {key} of type {typeof(T)} is required");
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersisterSettings.cs b/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersisterSettings.cs
new file mode 100644
index 0000000000..6789116fb7
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Abstractions/EFPersisterSettings.cs
@@ -0,0 +1,10 @@
+namespace ServiceControl.Persistence.EFCore.Abstractions;
+
+public abstract class EFPersisterSettings : PersistenceSettings
+{
+ public required string ConnectionString { get; set; }
+ public int CommandTimeout { get; set; } = 30;
+ public TimeSpan ErrorRetentionPeriod { get; set; }
+ public string? MessageBodyStoragePath { get; set; }
+ public int MinBodySizeForCompression { get; set; } = 4096;
+}
diff --git a/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs b/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs
new file mode 100644
index 0000000000..83f28e3310
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/DbContexts/ServiceControlDbContext.cs
@@ -0,0 +1,7 @@
+namespace ServiceControl.Persistence.EFCore.DbContexts;
+
+using Microsoft.EntityFrameworkCore;
+
+public abstract class ServiceControlDbContext(DbContextOptions options) : DbContext(options)
+{
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/BodyStorage.cs b/src/ServiceControl.Persistence.EFCore/Implementation/BodyStorage.cs
new file mode 100644
index 0000000000..e1caa893a7
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/BodyStorage.cs
@@ -0,0 +1,9 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using ServiceControl.Operations.BodyStorage;
+
+public class BodyStorage : IBodyStorage
+{
+ public Task TryFetch(string bodyId) =>
+ throw new NotImplementedException();
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/CustomCheckDataStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/CustomCheckDataStore.cs
new file mode 100644
index 0000000000..9a16177f3d
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/CustomCheckDataStore.cs
@@ -0,0 +1,19 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using ServiceControl.Contracts.CustomChecks;
+using ServiceControl.Persistence.Infrastructure;
+
+public class CustomCheckDataStore : ICustomChecksDataStore
+{
+ public Task UpdateCustomCheckStatus(CustomCheckDetail detail) =>
+ throw new NotImplementedException();
+
+ public Task>> GetStats(PagingInfo paging, string? status = null) =>
+ throw new NotImplementedException();
+
+ public Task DeleteCustomCheck(Guid id) =>
+ throw new NotImplementedException();
+
+ public Task GetNumberOfFailedChecks() =>
+ throw new NotImplementedException();
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/EditFailedMessagesManager.cs b/src/ServiceControl.Persistence.EFCore/Implementation/EditFailedMessagesManager.cs
new file mode 100644
index 0000000000..aa936dd6dc
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/EditFailedMessagesManager.cs
@@ -0,0 +1,27 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using ServiceControl.MessageFailures;
+
+public class EditFailedMessagesManager : IEditFailedMessagesManager
+{
+ public Task GetFailedMessage(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task GetCurrentEditingRequestId(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task SetCurrentEditingRequestId(string editingMessageId) =>
+ throw new NotImplementedException();
+
+ public Task SetFailedMessageAsResolved() =>
+ throw new NotImplementedException();
+
+ public Task SaveChanges() =>
+ throw new NotImplementedException();
+
+ public void Dispose()
+ {
+ // Nothing to dispose yet
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs
new file mode 100644
index 0000000000..7e79541e91
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/EndpointSettingsStore.cs
@@ -0,0 +1,13 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+public class EndpointSettingsStore : IEndpointSettingsStore
+{
+ public IAsyncEnumerable GetAllEndpointSettings() =>
+ throw new NotImplementedException();
+
+ public Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken token) =>
+ throw new NotImplementedException();
+
+ public Task Delete(string name, CancellationToken cancellationToken) =>
+ throw new NotImplementedException();
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/ErrorMessagesDataStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/ErrorMessagesDataStore.cs
new file mode 100644
index 0000000000..2757c59658
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/ErrorMessagesDataStore.cs
@@ -0,0 +1,111 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using ServiceControl.CompositeViews.Messages;
+using ServiceControl.EventLog;
+using ServiceControl.MessageFailures;
+using ServiceControl.MessageFailures.Api;
+using ServiceControl.Operations;
+using ServiceControl.Persistence.Infrastructure;
+using ServiceControl.Recoverability;
+
+public class ErrorMessagesDataStore : IErrorMessageDataStore
+{
+ public Task>> GetAllMessages(PagingInfo pagingInfo, SortInfo sortInfo, bool includeSystemMessages, DateTimeRange? timeSentRange = null) =>
+ throw new NotImplementedException();
+
+ public Task>> GetAllMessagesForEndpoint(string endpointName, PagingInfo pagingInfo, SortInfo sortInfo, bool includeSystemMessages, DateTimeRange? timeSentRange = null) =>
+ throw new NotImplementedException();
+
+ public Task>> GetAllMessagesByConversation(string conversationId, PagingInfo pagingInfo, SortInfo sortInfo, bool includeSystemMessages) =>
+ throw new NotImplementedException();
+
+ public Task>> GetAllMessagesForSearch(string searchTerms, PagingInfo pagingInfo, SortInfo sortInfo, DateTimeRange? timeSentRange = null) =>
+ throw new NotImplementedException();
+
+ public Task>> SearchEndpointMessages(string endpointName, string searchKeyword, PagingInfo pagingInfo, SortInfo sortInfo, DateTimeRange? timeSentRange = null) =>
+ throw new NotImplementedException();
+
+ public Task FailedMessageMarkAsArchived(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task FailedMessagesFetch(Guid[] ids) =>
+ throw new NotImplementedException();
+
+ public Task StoreFailedErrorImport(FailedErrorImport failure) =>
+ throw new NotImplementedException();
+
+ public Task CreateEditFailedMessageManager() =>
+ throw new NotImplementedException();
+
+ public Task> GetFailureGroupView(string groupId, string status, string modified) =>
+ throw new NotImplementedException();
+
+ public Task> GetFailureGroupsByClassifier(string classifier) =>
+ throw new NotImplementedException();
+
+ public Task>> ErrorGet(string status, string modified, string queueAddress, PagingInfo pagingInfo, SortInfo sortInfo) =>
+ throw new NotImplementedException();
+
+ public Task ErrorsHead(string status, string modified, string queueAddress) =>
+ throw new NotImplementedException();
+
+ public Task>> ErrorsByEndpointName(string status, string endpointName, string modified, PagingInfo pagingInfo, SortInfo sortInfo) =>
+ throw new NotImplementedException();
+
+ public Task> ErrorsSummary() =>
+ throw new NotImplementedException();
+
+ public Task ErrorLastBy(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task ErrorBy(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task CreateNotificationsManager() =>
+ throw new NotImplementedException();
+
+ public Task EditComment(string groupId, string comment) =>
+ throw new NotImplementedException();
+
+ public Task DeleteComment(string groupId) =>
+ throw new NotImplementedException();
+
+ public Task>> GetGroupErrors(string groupId, string status, string modified, SortInfo sortInfo, PagingInfo pagingInfo) =>
+ throw new NotImplementedException();
+
+ public Task GetGroupErrorsCount(string groupId, string status, string modified) =>
+ throw new NotImplementedException();
+
+ public Task>> GetGroup(string groupId, string status, string modified) =>
+ throw new NotImplementedException();
+
+ public Task MarkMessageAsResolved(string failedMessageId) =>
+ throw new NotImplementedException();
+
+ public Task ProcessPendingRetries(DateTime periodFrom, DateTime periodTo, string queueAddress, Func processCallback) =>
+ throw new NotImplementedException();
+
+ public Task UnArchiveMessagesByRange(DateTime from, DateTime to) =>
+ throw new NotImplementedException();
+
+ public Task UnArchiveMessages(IEnumerable failedMessageIds) =>
+ throw new NotImplementedException();
+
+ public Task RevertRetry(string messageUniqueId) =>
+ throw new NotImplementedException();
+
+ public Task RemoveFailedMessageRetryDocument(string uniqueMessageId) =>
+ throw new NotImplementedException();
+
+ public Task GetRetryPendingMessages(DateTime from, DateTime to, string queueAddress) =>
+ throw new NotImplementedException();
+
+ public Task FetchFromFailedMessage(string uniqueMessageId) =>
+ throw new NotImplementedException();
+
+ public Task StoreEventLogItem(EventLogItem logItem) =>
+ throw new NotImplementedException();
+
+ public Task StoreFailedMessagesForTestsOnly(params FailedMessage[] failedMessages) =>
+ throw new NotImplementedException();
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/EventLogDataStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/EventLogDataStore.cs
new file mode 100644
index 0000000000..1b9799b989
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/EventLogDataStore.cs
@@ -0,0 +1,13 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using ServiceControl.EventLog;
+using ServiceControl.Persistence.Infrastructure;
+
+public class EventLogDataStore : IEventLogDataStore
+{
+ public Task Add(EventLogItem logItem) =>
+ throw new NotImplementedException();
+
+ public Task<(IList items, long total, string version)> GetEventLogItems(PagingInfo pagingInfo) =>
+ throw new NotImplementedException();
+}
diff --git a/src/ServiceControl.Persistence.EFCore/Implementation/ExternalIntegrationRequestsDataStore.cs b/src/ServiceControl.Persistence.EFCore/Implementation/ExternalIntegrationRequestsDataStore.cs
new file mode 100644
index 0000000000..dfa5094631
--- /dev/null
+++ b/src/ServiceControl.Persistence.EFCore/Implementation/ExternalIntegrationRequestsDataStore.cs
@@ -0,0 +1,19 @@
+namespace ServiceControl.Persistence.EFCore.Implementation;
+
+using Microsoft.Extensions.Hosting;
+using ServiceControl.ExternalIntegrations;
+
+public class ExternalIntegrationRequestsDataStore : IExternalIntegrationRequestsDataStore, IHostedService
+{
+ public void Subscribe(Func