diff --git a/TickerQ.sln b/TickerQ.sln
index c448310a..ef9bb45b 100644
--- a/TickerQ.sln
+++ b/TickerQ.sln
@@ -40,6 +40,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TickerQ.Caching.StackExchan
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TickerQ.Instrumentation.OpenTelemetry", "src\TickerQ.Instrumentation.OpenTelemetry\TickerQ.Instrumentation.OpenTelemetry.csproj", "{D87B9599-22C7-4E82-B300-1239E504E097}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TickerQ.Sample.WebApi", "samples\TickerQ.Sample.WebApi\TickerQ.Sample.WebApi.csproj", "{4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TickerQ.Sample.Console", "samples\TickerQ.Sample.Console\TickerQ.Sample.Console.csproj", "{723F26D9-C675-4883-8B62-AEBA370566D4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -146,6 +150,30 @@ Global
{D87B9599-22C7-4E82-B300-1239E504E097}.Release|x64.Build.0 = Release|Any CPU
{D87B9599-22C7-4E82-B300-1239E504E097}.Release|x86.ActiveCfg = Release|Any CPU
{D87B9599-22C7-4E82-B300-1239E504E097}.Release|x86.Build.0 = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|x64.Build.0 = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Debug|x86.Build.0 = Debug|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|x64.ActiveCfg = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|x64.Build.0 = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|x86.ActiveCfg = Release|Any CPU
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E}.Release|x86.Build.0 = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|x64.Build.0 = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Debug|x86.Build.0 = Debug|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|x64.ActiveCfg = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|x64.Build.0 = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|x86.ActiveCfg = Release|Any CPU
+ {723F26D9-C675-4883-8B62-AEBA370566D4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -160,5 +188,7 @@ Global
{9E6EC713-AD6D-4909-8617-B909D76A6E3A} = {9A4EB4A4-FB92-477C-A6D8-0735579B7BAB}
{B3049104-9C15-4933-A440-CA62CFBC5F70} = {BA086F2D-2778-4F58-A9AA-45F560CE3504}
{D87B9599-22C7-4E82-B300-1239E504E097} = {BA086F2D-2778-4F58-A9AA-45F560CE3504}
+ {4DC9FE10-966F-4C8A-B7D2-1215DC319B9E} = {45D577FA-DB7A-4B96-BB3F-97DDA0A929D5}
+ {723F26D9-C675-4883-8B62-AEBA370566D4} = {45D577FA-DB7A-4B96-BB3F-97DDA0A929D5}
EndGlobalSection
EndGlobal
diff --git a/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.Designer.cs b/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.Designer.cs
new file mode 100644
index 00000000..09958c0f
--- /dev/null
+++ b/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.Designer.cs
@@ -0,0 +1,229 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TickerQ.EntityFrameworkCore.DbContextFactory;
+
+#nullable disable
+
+namespace TickerQ.Sample.Console.Migrations
+{
+ [DbContext(typeof(TickerQDbContext))]
+ [Migration("20251123154004_InitialTickerQOperationalStore")]
+ partial class InitialTickerQOperationalStore
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Expression")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Expression")
+ .HasDatabaseName("IX_CronTickers_Expression");
+
+ b.HasIndex("Function", "Expression")
+ .HasDatabaseName("IX_Function_Expression");
+
+ b.ToTable("CronTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("CronTickerId")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CronTickerId")
+ .HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
+
+ b.HasIndex("CronTickerId", "ExecutionTime")
+ .IsUnique()
+ .HasDatabaseName("UQ_CronTickerId_ExecutionTime");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
+
+ b.ToTable("CronTickerOccurrences", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("RunCondition")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_ExecutionTime");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_Status_ExecutionTime");
+
+ b.ToTable("TimeTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.CronTickerEntity", "CronTicker")
+ .WithMany()
+ .HasForeignKey("CronTickerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CronTicker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.TimeTickerEntity", "Parent")
+ .WithMany("Children")
+ .HasForeignKey("ParentId")
+ .OnDelete(DeleteBehavior.NoAction);
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Navigation("Children");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.cs b/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.cs
new file mode 100644
index 00000000..9768f32d
--- /dev/null
+++ b/samples/TickerQ.Sample.Console/Migrations/20251123154004_InitialTickerQOperationalStore.cs
@@ -0,0 +1,178 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace TickerQ.Sample.Console.Migrations
+{
+ ///
+ public partial class InitialTickerQOperationalStore : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "ticker");
+
+ migrationBuilder.CreateTable(
+ name: "CronTickers",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Expression = table.Column(type: "TEXT", nullable: true),
+ Request = table.Column(type: "BLOB", nullable: true),
+ Retries = table.Column(type: "INTEGER", nullable: false),
+ RetryIntervals = table.Column(type: "TEXT", nullable: true),
+ Function = table.Column(type: "TEXT", nullable: true),
+ Description = table.Column(type: "TEXT", nullable: true),
+ InitIdentifier = table.Column(type: "TEXT", nullable: true),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CronTickers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "TimeTickers",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Function = table.Column(type: "TEXT", nullable: true),
+ Description = table.Column(type: "TEXT", nullable: true),
+ InitIdentifier = table.Column(type: "TEXT", nullable: true),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false),
+ Status = table.Column(type: "INTEGER", nullable: false),
+ LockHolder = table.Column(type: "TEXT", nullable: true),
+ Request = table.Column(type: "BLOB", nullable: true),
+ ExecutionTime = table.Column(type: "TEXT", nullable: true),
+ LockedAt = table.Column(type: "TEXT", nullable: true),
+ ExecutedAt = table.Column(type: "TEXT", nullable: true),
+ ExceptionMessage = table.Column(type: "TEXT", nullable: true),
+ SkippedReason = table.Column(type: "TEXT", nullable: true),
+ ElapsedTime = table.Column(type: "INTEGER", nullable: false),
+ Retries = table.Column(type: "INTEGER", nullable: false),
+ RetryCount = table.Column(type: "INTEGER", nullable: false),
+ RetryIntervals = table.Column(type: "TEXT", nullable: true),
+ ParentId = table.Column(type: "TEXT", nullable: true),
+ RunCondition = table.Column(type: "INTEGER", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TimeTickers", x => x.Id);
+ table.ForeignKey(
+ name: "FK_TimeTickers_TimeTickers_ParentId",
+ column: x => x.ParentId,
+ principalSchema: "ticker",
+ principalTable: "TimeTickers",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CronTickerOccurrences",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Status = table.Column(type: "INTEGER", nullable: false),
+ LockHolder = table.Column(type: "TEXT", nullable: true),
+ ExecutionTime = table.Column(type: "TEXT", nullable: false),
+ CronTickerId = table.Column(type: "TEXT", nullable: false),
+ LockedAt = table.Column(type: "TEXT", nullable: true),
+ ExecutedAt = table.Column(type: "TEXT", nullable: true),
+ ExceptionMessage = table.Column(type: "TEXT", nullable: true),
+ SkippedReason = table.Column(type: "TEXT", nullable: true),
+ ElapsedTime = table.Column(type: "INTEGER", nullable: false),
+ RetryCount = table.Column(type: "INTEGER", nullable: false),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CronTickerOccurrences", x => x.Id);
+ table.ForeignKey(
+ name: "FK_CronTickerOccurrences_CronTickers_CronTickerId",
+ column: x => x.CronTickerId,
+ principalSchema: "ticker",
+ principalTable: "CronTickers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_CronTickerId",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ column: "CronTickerId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ column: "ExecutionTime");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_Status_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ columns: new[] { "Status", "ExecutionTime" });
+
+ migrationBuilder.CreateIndex(
+ name: "UQ_CronTickerId_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ columns: new[] { "CronTickerId", "ExecutionTime" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickers_Expression",
+ schema: "ticker",
+ table: "CronTickers",
+ column: "Expression");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Function_Expression",
+ schema: "ticker",
+ table: "CronTickers",
+ columns: new[] { "Function", "Expression" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTicker_ExecutionTime",
+ schema: "ticker",
+ table: "TimeTickers",
+ column: "ExecutionTime");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTicker_Status_ExecutionTime",
+ schema: "ticker",
+ table: "TimeTickers",
+ columns: new[] { "Status", "ExecutionTime" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTickers_ParentId",
+ schema: "ticker",
+ table: "TimeTickers",
+ column: "ParentId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "CronTickerOccurrences",
+ schema: "ticker");
+
+ migrationBuilder.DropTable(
+ name: "TimeTickers",
+ schema: "ticker");
+
+ migrationBuilder.DropTable(
+ name: "CronTickers",
+ schema: "ticker");
+ }
+ }
+}
diff --git a/samples/TickerQ.Sample.Console/Migrations/TickerQDbContextModelSnapshot.cs b/samples/TickerQ.Sample.Console/Migrations/TickerQDbContextModelSnapshot.cs
new file mode 100644
index 00000000..63d648cd
--- /dev/null
+++ b/samples/TickerQ.Sample.Console/Migrations/TickerQDbContextModelSnapshot.cs
@@ -0,0 +1,226 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TickerQ.EntityFrameworkCore.DbContextFactory;
+
+#nullable disable
+
+namespace TickerQ.Sample.Console.Migrations
+{
+ [DbContext(typeof(TickerQDbContext))]
+ partial class TickerQDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Expression")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Expression")
+ .HasDatabaseName("IX_CronTickers_Expression");
+
+ b.HasIndex("Function", "Expression")
+ .HasDatabaseName("IX_Function_Expression");
+
+ b.ToTable("CronTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("CronTickerId")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CronTickerId")
+ .HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
+
+ b.HasIndex("CronTickerId", "ExecutionTime")
+ .IsUnique()
+ .HasDatabaseName("UQ_CronTickerId_ExecutionTime");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
+
+ b.ToTable("CronTickerOccurrences", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("RunCondition")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_ExecutionTime");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_Status_ExecutionTime");
+
+ b.ToTable("TimeTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.CronTickerEntity", "CronTicker")
+ .WithMany()
+ .HasForeignKey("CronTickerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CronTicker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.TimeTickerEntity", "Parent")
+ .WithMany("Children")
+ .HasForeignKey("ParentId")
+ .OnDelete(DeleteBehavior.NoAction);
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Navigation("Children");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/samples/TickerQ.Sample.Console/Program.cs b/samples/TickerQ.Sample.Console/Program.cs
new file mode 100644
index 00000000..a04d5a9e
--- /dev/null
+++ b/samples/TickerQ.Sample.Console/Program.cs
@@ -0,0 +1,84 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using TickerQ.DependencyInjection;
+using TickerQ.EntityFrameworkCore.DbContextFactory;
+using TickerQ.EntityFrameworkCore.DependencyInjection;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Base;
+using TickerQ.Utilities.Entities;
+using TickerQ.Utilities.Interfaces.Managers;
+
+var host = Host.CreateDefaultBuilder(args)
+ .ConfigureServices((context, services) =>
+ {
+ // Configure TickerQ with SQLite operational store (file-based)
+ services.AddTickerQ(options =>
+ {
+ options.AddOperationalStore(efOptions =>
+ {
+ efOptions.UseTickerQDbContext(dbOptions =>
+ {
+ dbOptions.UseSqlite(
+ "Data Source=tickerq-console.db",
+ b => b.MigrationsAssembly("TickerQ.Sample.Console"));
+ });
+ });
+ });
+
+ services.AddHostedService();
+ })
+ .Build();
+
+// Ensure TickerQ operational store schema is applied
+using (var scope = host.Services.CreateScope())
+{
+ var db = scope.ServiceProvider.GetRequiredService();
+ db.Database.Migrate();
+}
+
+// Build function metadata so TickerFunctionProvider.TickerFunctions is initialized
+TickerFunctionProvider.Build();
+
+await host.RunAsync();
+
+// Simple sample job
+public class ConsoleSampleJobs
+{
+ [TickerFunction("ConsoleSample_HelloWorld")]
+ public Task HelloWorldAsync(TickerFunctionContext context, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"[Console] Hello from TickerQ! Id={context.Id}, ScheduledFor={context.ScheduledFor:O}");
+ return Task.CompletedTask;
+ }
+}
+
+// Hosted service that schedules a single job on startup
+public class SampleScheduler : IHostedService
+{
+ private readonly ITimeTickerManager _timeTickerManager;
+
+ public SampleScheduler(ITimeTickerManager timeTickerManager)
+ {
+ _timeTickerManager = timeTickerManager;
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ var result = await _timeTickerManager.AddAsync(new TimeTickerEntity
+ {
+ Function = "ConsoleSample_HelloWorld",
+ ExecutionTime = DateTime.UtcNow.AddSeconds(5)
+ }, cancellationToken);
+
+ if (!result.IsSucceeded)
+ {
+ Console.WriteLine($"Failed to schedule console sample job. Exception: {result.Exception}");
+ return;
+ }
+
+ Console.WriteLine($"Scheduled console sample job with Id={result.Result.Id}, ScheduledFor={result.Result.ExecutionTime:O}");
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+}
diff --git a/samples/TickerQ.Sample.Console/TickerQ.Sample.Console.csproj b/samples/TickerQ.Sample.Console/TickerQ.Sample.Console.csproj
new file mode 100644
index 00000000..ed863b6f
--- /dev/null
+++ b/samples/TickerQ.Sample.Console/TickerQ.Sample.Console.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.Designer.cs b/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.Designer.cs
new file mode 100644
index 00000000..87405b90
--- /dev/null
+++ b/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.Designer.cs
@@ -0,0 +1,229 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TickerQ.EntityFrameworkCore.DbContextFactory;
+
+#nullable disable
+
+namespace TickerQ.Sample.WebApi.Migrations
+{
+ [DbContext(typeof(TickerQDbContext))]
+ [Migration("20251123154016_InitialTickerQOperationalStore")]
+ partial class InitialTickerQOperationalStore
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Expression")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Expression")
+ .HasDatabaseName("IX_CronTickers_Expression");
+
+ b.HasIndex("Function", "Expression")
+ .HasDatabaseName("IX_Function_Expression");
+
+ b.ToTable("CronTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("CronTickerId")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CronTickerId")
+ .HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
+
+ b.HasIndex("CronTickerId", "ExecutionTime")
+ .IsUnique()
+ .HasDatabaseName("UQ_CronTickerId_ExecutionTime");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
+
+ b.ToTable("CronTickerOccurrences", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ParentId")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("RunCondition")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_ExecutionTime");
+
+ b.HasIndex("ParentId");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_TimeTicker_Status_ExecutionTime");
+
+ b.ToTable("TimeTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.CronTickerEntity", "CronTicker")
+ .WithMany()
+ .HasForeignKey("CronTickerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CronTicker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.HasOne("TickerQ.Utilities.Entities.TimeTickerEntity", "Parent")
+ .WithMany("Children")
+ .HasForeignKey("ParentId")
+ .OnDelete(DeleteBehavior.NoAction);
+
+ b.Navigation("Parent");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Navigation("Children");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.cs b/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.cs
new file mode 100644
index 00000000..6c94dd30
--- /dev/null
+++ b/samples/TickerQ.Sample.WebApi/Migrations/20251123154016_InitialTickerQOperationalStore.cs
@@ -0,0 +1,178 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace TickerQ.Sample.WebApi.Migrations
+{
+ ///
+ public partial class InitialTickerQOperationalStore : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "ticker");
+
+ migrationBuilder.CreateTable(
+ name: "CronTickers",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Expression = table.Column(type: "TEXT", nullable: true),
+ Request = table.Column(type: "BLOB", nullable: true),
+ Retries = table.Column(type: "INTEGER", nullable: false),
+ RetryIntervals = table.Column(type: "TEXT", nullable: true),
+ Function = table.Column(type: "TEXT", nullable: true),
+ Description = table.Column(type: "TEXT", nullable: true),
+ InitIdentifier = table.Column(type: "TEXT", nullable: true),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CronTickers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "TimeTickers",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Function = table.Column(type: "TEXT", nullable: true),
+ Description = table.Column(type: "TEXT", nullable: true),
+ InitIdentifier = table.Column(type: "TEXT", nullable: true),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false),
+ Status = table.Column(type: "INTEGER", nullable: false),
+ LockHolder = table.Column(type: "TEXT", nullable: true),
+ Request = table.Column(type: "BLOB", nullable: true),
+ ExecutionTime = table.Column(type: "TEXT", nullable: true),
+ LockedAt = table.Column(type: "TEXT", nullable: true),
+ ExecutedAt = table.Column(type: "TEXT", nullable: true),
+ ExceptionMessage = table.Column(type: "TEXT", nullable: true),
+ SkippedReason = table.Column(type: "TEXT", nullable: true),
+ ElapsedTime = table.Column(type: "INTEGER", nullable: false),
+ Retries = table.Column(type: "INTEGER", nullable: false),
+ RetryCount = table.Column(type: "INTEGER", nullable: false),
+ RetryIntervals = table.Column(type: "TEXT", nullable: true),
+ ParentId = table.Column(type: "TEXT", nullable: true),
+ RunCondition = table.Column(type: "INTEGER", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_TimeTickers", x => x.Id);
+ table.ForeignKey(
+ name: "FK_TimeTickers_TimeTickers_ParentId",
+ column: x => x.ParentId,
+ principalSchema: "ticker",
+ principalTable: "TimeTickers",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "CronTickerOccurrences",
+ schema: "ticker",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Status = table.Column(type: "INTEGER", nullable: false),
+ LockHolder = table.Column(type: "TEXT", nullable: true),
+ ExecutionTime = table.Column(type: "TEXT", nullable: false),
+ CronTickerId = table.Column(type: "TEXT", nullable: false),
+ LockedAt = table.Column(type: "TEXT", nullable: true),
+ ExecutedAt = table.Column(type: "TEXT", nullable: true),
+ ExceptionMessage = table.Column(type: "TEXT", nullable: true),
+ SkippedReason = table.Column(type: "TEXT", nullable: true),
+ ElapsedTime = table.Column(type: "INTEGER", nullable: false),
+ RetryCount = table.Column(type: "INTEGER", nullable: false),
+ CreatedAt = table.Column(type: "TEXT", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CronTickerOccurrences", x => x.Id);
+ table.ForeignKey(
+ name: "FK_CronTickerOccurrences_CronTickers_CronTickerId",
+ column: x => x.CronTickerId,
+ principalSchema: "ticker",
+ principalTable: "CronTickers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_CronTickerId",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ column: "CronTickerId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ column: "ExecutionTime");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickerOccurrence_Status_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ columns: new[] { "Status", "ExecutionTime" });
+
+ migrationBuilder.CreateIndex(
+ name: "UQ_CronTickerId_ExecutionTime",
+ schema: "ticker",
+ table: "CronTickerOccurrences",
+ columns: new[] { "CronTickerId", "ExecutionTime" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_CronTickers_Expression",
+ schema: "ticker",
+ table: "CronTickers",
+ column: "Expression");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Function_Expression",
+ schema: "ticker",
+ table: "CronTickers",
+ columns: new[] { "Function", "Expression" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTicker_ExecutionTime",
+ schema: "ticker",
+ table: "TimeTickers",
+ column: "ExecutionTime");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTicker_Status_ExecutionTime",
+ schema: "ticker",
+ table: "TimeTickers",
+ columns: new[] { "Status", "ExecutionTime" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_TimeTickers_ParentId",
+ schema: "ticker",
+ table: "TimeTickers",
+ column: "ParentId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "CronTickerOccurrences",
+ schema: "ticker");
+
+ migrationBuilder.DropTable(
+ name: "TimeTickers",
+ schema: "ticker");
+
+ migrationBuilder.DropTable(
+ name: "CronTickers",
+ schema: "ticker");
+ }
+ }
+}
diff --git a/samples/TickerQ.Sample.WebApi/Migrations/TickerQDbContextModelSnapshot.cs b/samples/TickerQ.Sample.WebApi/Migrations/TickerQDbContextModelSnapshot.cs
new file mode 100644
index 00000000..81c3d049
--- /dev/null
+++ b/samples/TickerQ.Sample.WebApi/Migrations/TickerQDbContextModelSnapshot.cs
@@ -0,0 +1,226 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using TickerQ.EntityFrameworkCore.DbContextFactory;
+
+#nullable disable
+
+namespace TickerQ.Sample.WebApi.Migrations
+{
+ [DbContext(typeof(TickerQDbContext))]
+ partial class TickerQDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.0");
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("Expression")
+ .HasColumnType("TEXT");
+
+ b.Property("Function")
+ .HasColumnType("TEXT");
+
+ b.Property("InitIdentifier")
+ .HasColumnType("TEXT");
+
+ b.Property("Request")
+ .HasColumnType("BLOB");
+
+ b.Property("Retries")
+ .HasColumnType("INTEGER");
+
+ b.PrimitiveCollection("RetryIntervals")
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Expression")
+ .HasDatabaseName("IX_CronTickers_Expression");
+
+ b.HasIndex("Function", "Expression")
+ .HasDatabaseName("IX_Function_Expression");
+
+ b.ToTable("CronTickers", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.CronTickerOccurrenceEntity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("CronTickerId")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExceptionMessage")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExecutionTime")
+ .HasColumnType("TEXT");
+
+ b.Property("LockHolder")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("SkippedReason")
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CronTickerId")
+ .HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
+
+ b.HasIndex("ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
+
+ b.HasIndex("CronTickerId", "ExecutionTime")
+ .IsUnique()
+ .HasDatabaseName("UQ_CronTickerId_ExecutionTime");
+
+ b.HasIndex("Status", "ExecutionTime")
+ .HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
+
+ b.ToTable("CronTickerOccurrences", "ticker");
+ });
+
+ modelBuilder.Entity("TickerQ.Utilities.Entities.TimeTickerEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("ElapsedTime")
+ .HasColumnType("INTEGER");
+
+ b.Property