using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using w4c_workflows.Models; namespace w4c_workflows.Data; public class WorkflowsDbContext : DbContext { public WorkflowsDbContext(DbContextOptions options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); // Additive schema (WorkflowTask.ArchivedAt for W7, WorkflowTask.Server for S8) is // applied by idempotent raw SQL at startup — the project's house rule forbids new // EF migrations. Suppress EF Core 10's "pending model changes" error so // MigrateAsync (which only applies recorded migrations) does not reject those // migration-less columns on a long-lived database. Runtime queries are unaffected. optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); } public DbSet Workflows => Set(); public DbSet Tasks => Set(); public DbSet WorkflowRuns => Set(); public DbSet TaskRuns => Set(); public DbSet DurableStates => Set(); public DbSet ApiKeys => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { // Separate schema so the workflow tables never collide with the rest of // the shared w4c database. modelBuilder.HasDefaultSchema("workflows"); modelBuilder.Entity(e => { e.HasKey(w => w.Id); e.Property(w => w.Id).ValueGeneratedOnAdd(); e.Property(w => w.Name).HasMaxLength(200).IsRequired(); e.Property(w => w.Path).HasMaxLength(500).IsRequired(); e.Property(w => w.GitSha).HasMaxLength(64); e.Property(w => w.Status).HasMaxLength(32).IsRequired(); e.Property(w => w.Mode).HasMaxLength(32).IsRequired(); e.Property(w => w.Target).HasMaxLength(100).IsRequired(); e.Property(w => w.TriggerJson).HasColumnType("jsonb"); e.HasIndex(w => new { w.TenantId, w.Name }).IsUnique(); e.HasIndex(w => w.TenantId); }); modelBuilder.Entity(e => { e.HasKey(t => t.Id); e.Property(t => t.Id).ValueGeneratedOnAdd(); e.Property(t => t.Key).HasMaxLength(100).IsRequired(); e.Property(t => t.Language).HasMaxLength(32).IsRequired(); e.Property(t => t.Mode).HasMaxLength(32).IsRequired(); e.Property(t => t.EntryJson).HasColumnType("jsonb"); e.Property(t => t.EnvJson).HasColumnType("jsonb"); e.HasOne(t => t.Workflow) .WithMany(w => w.Tasks) .HasForeignKey(t => t.WorkflowId) .OnDelete(DeleteBehavior.Cascade); e.HasIndex(t => t.WorkflowId); // Preserve historical TaskRuns when a task is removed from the YAML: // removed tasks are archived (ArchivedAt != null) rather than deleted, and // every "active workflow definition" query excludes them automatically. e.HasQueryFilter(t => t.ArchivedAt == null); }); modelBuilder.Entity(e => { e.HasKey(r => r.Id); e.Property(r => r.Id).ValueGeneratedOnAdd(); e.Property(r => r.Status).HasMaxLength(32).IsRequired(); e.Property(r => r.TriggerJson).HasColumnType("jsonb"); e.Property(r => r.InputJson).HasColumnType("jsonb"); e.Property(r => r.OutputJson).HasColumnType("jsonb"); e.Property(r => r.Error).HasColumnType("text"); e.Property(r => r.CorrelationId).HasMaxLength(128); e.Property(r => r.StartTaskId); e.Property(r => r.CompensationPlanJson).HasColumnType("jsonb"); e.HasOne(r => r.Workflow) .WithMany() .HasForeignKey(r => r.WorkflowId) .OnDelete(DeleteBehavior.Restrict); e.HasIndex(r => r.TenantId); e.HasIndex(r => r.CorrelationId); }); modelBuilder.Entity(e => { e.HasKey(t => t.Id); e.Property(t => t.Id).ValueGeneratedOnAdd(); e.Property(t => t.Status).HasMaxLength(32).IsRequired(); e.Property(t => t.InputJson).HasColumnType("jsonb"); e.Property(t => t.OutputJson).HasColumnType("jsonb"); e.Property(t => t.Error).HasColumnType("text"); e.HasOne(t => t.Run) .WithMany(r => r.TaskRuns) .HasForeignKey(t => t.RunId) .OnDelete(DeleteBehavior.Cascade); e.HasOne(t => t.Task) .WithMany() .HasForeignKey(t => t.TaskId) .OnDelete(DeleteBehavior.Restrict); e.HasIndex(t => t.RunId); }); modelBuilder.Entity(e => { e.HasKey(d => d.InstanceId); e.Property(d => d.InstanceId).HasMaxLength(128); e.Property(d => d.StateJson).HasColumnType("jsonb"); e.Property(d => d.CorrelationId).HasMaxLength(128); e.HasIndex(d => d.CorrelationId); }); modelBuilder.Entity(e => { e.HasKey(k => k.Id); e.Property(k => k.Id).ValueGeneratedOnAdd(); e.Property(k => k.TenantId).IsRequired(); e.Property(k => k.KeyHash).HasMaxLength(64).IsRequired(); e.Property(k => k.Label).HasMaxLength(200).IsRequired(); e.Property(k => k.ScopesJson).HasColumnType("jsonb"); e.HasIndex(k => k.TenantId); }); } }