diff --git a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.Designer.cs b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.Designer.cs
similarity index 88%
rename from samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.Designer.cs
rename to samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.Designer.cs
index 63a5ccb..d4965ac 100644
--- a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.Designer.cs
+++ b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.Designer.cs
@@ -12,7 +12,7 @@
namespace Atomizer.EFCore.Example.Data.MySql.Migrations
{
[DbContext(typeof(ExampleMySqlContext))]
- [Migration("20260504195004_Initial")]
+ [Migration("20260505152241_Initial")]
partial class Initial
{
///
@@ -60,6 +60,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", (string)null);
});
@@ -133,6 +136,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", (string)null);
});
@@ -186,10 +204,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("tinyint(1)");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("varchar(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -207,6 +221,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("datetime(6)");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("varchar(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("longtext");
@@ -242,7 +260,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", (string)null);
});
diff --git a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.cs b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.cs
similarity index 87%
rename from samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.cs
rename to samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.cs
index 4dc5ffd..8a4cb7a 100644
--- a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260504195004_Initial.cs
+++ b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/20260505152241_Initial.cs
@@ -178,12 +178,54 @@ protected override void Up(MigrationBuilder migrationBuilder)
)
.Annotation("MySql:CharSet", "utf8mb4");
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId",
+ table: "AtomizerActiveServers",
+ columns: new[] { "LastHeartbeatAt", "InstanceId" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerJobErrors_JobId",
table: "AtomizerJobErrors",
column: "JobId"
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_IdempotencyKey",
+ table: "AtomizerJobs",
+ column: "IdempotencyKey"
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "PartitionKey", "SequenceNumber" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "Attempts", "PartitionKey" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "ScheduledAt", "Id" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_Status_LeaseToken",
+ table: "AtomizerJobs",
+ columns: new[] { "Status", "LeaseToken" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerSchedules_Enabled_NextRunAt_Id",
+ table: "AtomizerSchedules",
+ columns: new[] { "Enabled", "NextRunAt", "Id" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerSchedules_JobKey",
table: "AtomizerSchedules",
diff --git a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/ExampleMySqlContextModelSnapshot.cs b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/ExampleMySqlContextModelSnapshot.cs
index 21aa76e..12b87ee 100644
--- a/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/ExampleMySqlContextModelSnapshot.cs
+++ b/samples/Atomizer.EFCore.Example/Data/MySql/Migrations/ExampleMySqlContextModelSnapshot.cs
@@ -57,6 +57,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", (string)null);
});
@@ -130,6 +133,21 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", (string)null);
});
@@ -183,10 +201,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("tinyint(1)");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("varchar(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -204,6 +218,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("datetime(6)");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("varchar(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("longtext");
@@ -239,7 +257,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", (string)null);
});
diff --git a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.Designer.cs b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.Designer.cs
similarity index 88%
rename from samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.Designer.cs
rename to samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.Designer.cs
index a8c62e7..124f5f1 100644
--- a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.Designer.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.Designer.cs
@@ -12,7 +12,7 @@
namespace Atomizer.EFCore.Example.Data.Postgres.Migrations
{
[DbContext(typeof(ExamplePostgresContext))]
- [Migration("20260504194951_Initial")]
+ [Migration("20260505152231_Initial")]
partial class Initial
{
///
@@ -60,6 +60,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -133,6 +136,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -186,10 +204,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("boolean");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("character varying(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -207,6 +221,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("timestamp with time zone");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("text");
@@ -242,7 +260,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.cs b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.cs
similarity index 83%
rename from samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.cs
rename to samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.cs
index 0390093..1fad2fc 100644
--- a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260504194951_Initial.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/20260505152231_Initial.cs
@@ -165,6 +165,13 @@ protected override void Up(MigrationBuilder migrationBuilder)
}
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId",
+ schema: "Atomizer",
+ table: "AtomizerActiveServers",
+ columns: new[] { "LastHeartbeatAt", "InstanceId" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerJobErrors_JobId",
schema: "Atomizer",
@@ -172,6 +179,48 @@ protected override void Up(MigrationBuilder migrationBuilder)
column: "JobId"
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_IdempotencyKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ column: "IdempotencyKey"
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "PartitionKey", "SequenceNumber" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "Attempts", "PartitionKey" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "ScheduledAt", "Id" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_Status_LeaseToken",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "Status", "LeaseToken" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerSchedules_Enabled_NextRunAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerSchedules",
+ columns: new[] { "Enabled", "NextRunAt", "Id" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerSchedules_JobKey",
schema: "Atomizer",
diff --git a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/ExamplePostgresContextModelSnapshot.cs b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/ExamplePostgresContextModelSnapshot.cs
index d50726f..c9e51b6 100644
--- a/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/ExamplePostgresContextModelSnapshot.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Postgres/Migrations/ExamplePostgresContextModelSnapshot.cs
@@ -57,6 +57,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -130,6 +133,21 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -183,10 +201,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("boolean");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("character varying(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -204,6 +218,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("timestamp with time zone");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("text");
@@ -239,7 +257,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.Designer.cs b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.Designer.cs
similarity index 88%
rename from samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.Designer.cs
rename to samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.Designer.cs
index a2172ab..107f373 100644
--- a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.Designer.cs
+++ b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.Designer.cs
@@ -12,7 +12,7 @@
namespace Atomizer.EFCore.Example.Data.SqlServer.Migrations
{
[DbContext(typeof(ExampleSqlServerContext))]
- [Migration("20260504194956_Initial")]
+ [Migration("20260505152234_Initial")]
partial class Initial
{
///
@@ -60,6 +60,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -133,6 +136,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -186,10 +204,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("bit");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("nvarchar(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -207,6 +221,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("datetimeoffset");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("nvarchar(max)");
@@ -242,7 +260,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.cs b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.cs
similarity index 81%
rename from samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.cs
rename to samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.cs
index 097f394..4057f6a 100644
--- a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260504194956_Initial.cs
+++ b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/20260505152234_Initial.cs
@@ -129,6 +129,13 @@ protected override void Up(MigrationBuilder migrationBuilder)
}
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId",
+ schema: "Atomizer",
+ table: "AtomizerActiveServers",
+ columns: new[] { "LastHeartbeatAt", "InstanceId" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerJobErrors_JobId",
schema: "Atomizer",
@@ -136,6 +143,48 @@ protected override void Up(MigrationBuilder migrationBuilder)
column: "JobId"
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_IdempotencyKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ column: "IdempotencyKey"
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "PartitionKey", "SequenceNumber" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "Attempts", "PartitionKey" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "ScheduledAt", "Id" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_Status_LeaseToken",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "Status", "LeaseToken" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerSchedules_Enabled_NextRunAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerSchedules",
+ columns: new[] { "Enabled", "NextRunAt", "Id" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerSchedules_JobKey",
schema: "Atomizer",
diff --git a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/ExampleSqlServerContextModelSnapshot.cs b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/ExampleSqlServerContextModelSnapshot.cs
index 9571a29..3b5d747 100644
--- a/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/ExampleSqlServerContextModelSnapshot.cs
+++ b/samples/Atomizer.EFCore.Example/Data/SqlServer/Migrations/ExampleSqlServerContextModelSnapshot.cs
@@ -57,6 +57,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -130,6 +133,21 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -183,10 +201,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("bit");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("nvarchar(255)");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -204,6 +218,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("datetimeoffset");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("nvarchar(255)");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("nvarchar(max)");
@@ -239,7 +257,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.Designer.cs b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.Designer.cs
similarity index 87%
rename from samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.Designer.cs
rename to samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.Designer.cs
index 3b15222..731b370 100644
--- a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.Designer.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.Designer.cs
@@ -11,7 +11,7 @@
namespace Atomizer.EFCore.Example.Data.Sqlite.Migrations
{
[DbContext(typeof(ExampleSqliteContext))]
- [Migration("20260504195000_Initial")]
+ [Migration("20260505152238_Initial")]
partial class Initial
{
///
@@ -55,6 +55,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -128,6 +131,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -181,10 +199,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("INTEGER");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("TEXT");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -202,6 +216,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("TEXT");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("TEXT");
@@ -237,7 +255,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.cs b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.cs
similarity index 80%
rename from samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.cs
rename to samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.cs
index ec391d1..aa4c37a 100644
--- a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260504195000_Initial.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/20260505152238_Initial.cs
@@ -129,6 +129,13 @@ protected override void Up(MigrationBuilder migrationBuilder)
}
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId",
+ schema: "Atomizer",
+ table: "AtomizerActiveServers",
+ columns: new[] { "LastHeartbeatAt", "InstanceId" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerJobErrors_JobId",
schema: "Atomizer",
@@ -136,6 +143,48 @@ protected override void Up(MigrationBuilder migrationBuilder)
column: "JobId"
);
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_IdempotencyKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ column: "IdempotencyKey"
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "PartitionKey", "SequenceNumber" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "Attempts", "PartitionKey" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "QueueKey", "Status", "ScheduledAt", "Id" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerJobs_Status_LeaseToken",
+ schema: "Atomizer",
+ table: "AtomizerJobs",
+ columns: new[] { "Status", "LeaseToken" }
+ );
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AtomizerSchedules_Enabled_NextRunAt_Id",
+ schema: "Atomizer",
+ table: "AtomizerSchedules",
+ columns: new[] { "Enabled", "NextRunAt", "Id" }
+ );
+
migrationBuilder.CreateIndex(
name: "IX_AtomizerSchedules_JobKey",
schema: "Atomizer",
diff --git a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/ExampleSqliteContextModelSnapshot.cs b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/ExampleSqliteContextModelSnapshot.cs
index e0a32cf..191784d 100644
--- a/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/ExampleSqliteContextModelSnapshot.cs
+++ b/samples/Atomizer.EFCore.Example/Data/Sqlite/Migrations/ExampleSqliteContextModelSnapshot.cs
@@ -52,6 +52,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("InstanceId");
+ b.HasIndex("LastHeartbeatAt", "InstanceId")
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
+
b.ToTable("AtomizerActiveServers", "Atomizer");
});
@@ -125,6 +128,21 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
+ b.HasIndex("IdempotencyKey")
+ .HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
+
+ b.HasIndex("Status", "LeaseToken")
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+
+ b.HasIndex("QueueKey", "PartitionKey", "SequenceNumber")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+
+ b.HasIndex("QueueKey", "Status", "Attempts", "PartitionKey")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+
+ b.HasIndex("QueueKey", "Status", "ScheduledAt", "Id")
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+
b.ToTable("AtomizerJobs", "Atomizer");
});
@@ -178,10 +196,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Enabled")
.HasColumnType("INTEGER");
- b.Property("PartitionKey")
- .HasMaxLength(255)
- .HasColumnType("TEXT");
-
b.Property("JobKey")
.IsRequired()
.HasMaxLength(255)
@@ -199,6 +213,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("NextRunAt")
.HasColumnType("TEXT");
+ b.Property("PartitionKey")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
b.Property("Payload")
.IsRequired()
.HasColumnType("TEXT");
@@ -234,7 +252,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
b.HasIndex("JobKey")
- .IsUnique();
+ .IsUnique()
+ .HasDatabaseName("IX_AtomizerSchedules_JobKey");
+
+ b.HasIndex("Enabled", "NextRunAt", "Id")
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
b.ToTable("AtomizerSchedules", "Atomizer");
});
diff --git a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerActiveServerEntityConfiguration.cs b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerActiveServerEntityConfiguration.cs
index c4413ee..f5dac9d 100644
--- a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerActiveServerEntityConfiguration.cs
+++ b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerActiveServerEntityConfiguration.cs
@@ -28,5 +28,8 @@ public void Configure(EntityTypeBuilder builder)
builder.HasKey(server => server.InstanceId);
builder.Property(server => server.InstanceId).IsRequired().HasMaxLength(512).ValueGeneratedNever();
builder.Property(server => server.LastHeartbeatAt).IsRequired();
+ builder
+ .HasIndex(server => new { server.LastHeartbeatAt, server.InstanceId })
+ .HasDatabaseName("IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId");
}
}
diff --git a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerJobEntityConfiguration.cs b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerJobEntityConfiguration.cs
index 628e6f1..8553293 100644
--- a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerJobEntityConfiguration.cs
+++ b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerJobEntityConfiguration.cs
@@ -62,5 +62,36 @@ public void Configure(EntityTypeBuilder builder)
);
builder.Property(job => job.PartitionKey).HasMaxLength(255).IsRequired(false);
builder.Property(job => job.SequenceNumber).IsRequired(false);
+
+ builder
+ .HasIndex(job => new
+ {
+ job.QueueKey,
+ job.Status,
+ job.ScheduledAt,
+ job.Id,
+ })
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id");
+ builder
+ .HasIndex(job => new
+ {
+ job.QueueKey,
+ job.PartitionKey,
+ job.SequenceNumber,
+ })
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber");
+ builder
+ .HasIndex(job => new
+ {
+ job.QueueKey,
+ job.Status,
+ job.Attempts,
+ job.PartitionKey,
+ })
+ .HasDatabaseName("IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey");
+ builder
+ .HasIndex(job => new { job.Status, job.LeaseToken })
+ .HasDatabaseName("IX_AtomizerJobs_Status_LeaseToken");
+ builder.HasIndex(job => job.IdempotencyKey).HasDatabaseName("IX_AtomizerJobs_IdempotencyKey");
}
}
diff --git a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerScheduleEntityConfiguration.cs b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerScheduleEntityConfiguration.cs
index 9667576..a547501 100644
--- a/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerScheduleEntityConfiguration.cs
+++ b/src/Atomizer.EntityFrameworkCore/Configurations/AtomizerScheduleEntityConfiguration.cs
@@ -61,6 +61,14 @@ public void Configure(EntityTypeBuilder builder)
)
);
- builder.HasIndex(e => e.JobKey).IsUnique();
+ builder
+ .HasIndex(e => new
+ {
+ e.Enabled,
+ e.NextRunAt,
+ e.Id,
+ })
+ .HasDatabaseName("IX_AtomizerSchedules_Enabled_NextRunAt_Id");
+ builder.HasIndex(e => e.JobKey).IsUnique().HasDatabaseName("IX_AtomizerSchedules_JobKey");
}
}
diff --git a/tests/Atomizer.EntityFrameworkCore.Tests/Configurations/AtomizerEntityConfigurationTests.cs b/tests/Atomizer.EntityFrameworkCore.Tests/Configurations/AtomizerEntityConfigurationTests.cs
new file mode 100644
index 0000000..caec980
--- /dev/null
+++ b/tests/Atomizer.EntityFrameworkCore.Tests/Configurations/AtomizerEntityConfigurationTests.cs
@@ -0,0 +1,115 @@
+using Atomizer.EntityFrameworkCore.Entities;
+using Atomizer.EntityFrameworkCore.Tests.TestSetup;
+using AwesomeAssertions;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata;
+
+namespace Atomizer.EntityFrameworkCore.Tests.Configurations;
+
+public sealed class AtomizerEntityConfigurationTests
+{
+ [Fact]
+ public void AddAtomizerEntities_WhenConfiguringJobEntity_ShouldCreateQueryIndexes()
+ {
+ using var dbContext = CreateDbContext();
+
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerJobs_QueueKey_Status_ScheduledAt_Id",
+ [
+ nameof(AtomizerJobEntity.QueueKey),
+ nameof(AtomizerJobEntity.Status),
+ nameof(AtomizerJobEntity.ScheduledAt),
+ nameof(AtomizerJobEntity.Id),
+ ]
+ );
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerJobs_QueueKey_PartitionKey_SequenceNumber",
+ [
+ nameof(AtomizerJobEntity.QueueKey),
+ nameof(AtomizerJobEntity.PartitionKey),
+ nameof(AtomizerJobEntity.SequenceNumber),
+ ]
+ );
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerJobs_QueueKey_Status_Attempts_PartitionKey",
+ [
+ nameof(AtomizerJobEntity.QueueKey),
+ nameof(AtomizerJobEntity.Status),
+ nameof(AtomizerJobEntity.Attempts),
+ nameof(AtomizerJobEntity.PartitionKey),
+ ]
+ );
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerJobs_Status_LeaseToken",
+ [nameof(AtomizerJobEntity.Status), nameof(AtomizerJobEntity.LeaseToken)]
+ );
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerJobs_IdempotencyKey",
+ [nameof(AtomizerJobEntity.IdempotencyKey)]
+ );
+ }
+
+ [Fact]
+ public void AddAtomizerEntities_WhenConfiguringScheduleEntity_ShouldCreateQueryIndexes()
+ {
+ using var dbContext = CreateDbContext();
+
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerSchedules_Enabled_NextRunAt_Id",
+ [
+ nameof(AtomizerScheduleEntity.Enabled),
+ nameof(AtomizerScheduleEntity.NextRunAt),
+ nameof(AtomizerScheduleEntity.Id),
+ ]
+ );
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerSchedules_JobKey",
+ [nameof(AtomizerScheduleEntity.JobKey)],
+ isUnique: true
+ );
+ }
+
+ [Fact]
+ public void AddAtomizerEntities_WhenConfiguringActiveServerEntity_ShouldCreateQueryIndexes()
+ {
+ using var dbContext = CreateDbContext();
+
+ AssertIndex(
+ dbContext,
+ "IX_AtomizerActiveServers_LastHeartbeatAt_InstanceId",
+ [nameof(AtomizerActiveServerEntity.LastHeartbeatAt), nameof(AtomizerActiveServerEntity.InstanceId)]
+ );
+ }
+
+ private static IndexModelDbContext CreateDbContext()
+ {
+ var options = new DbContextOptionsBuilder().UseSqlite("Data Source=:memory:").Options;
+
+ return new IndexModelDbContext(options);
+ }
+
+ private static void AssertIndex(
+ DbContext dbContext,
+ string indexName,
+ string[] propertyNames,
+ bool isUnique = false
+ )
+ {
+ var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
+ entityType.Should().NotBeNull();
+
+ var index = entityType!.GetIndexes().SingleOrDefault(index => index.GetDatabaseName() == indexName);
+ index.Should().NotBeNull();
+ index!.Properties.Select(property => property.Name).Should().Equal(propertyNames);
+ index.IsUnique.Should().Be(isUnique);
+ }
+
+ private sealed class IndexModelDbContext(DbContextOptions options) : TestDbContext(options);
+}