Phase 1 WP-1: EF Core DbContext with Fluent API mappings for all 26 entities

ScadaLinkDbContext with 10 configuration classes (Fluent API only), initial
migration creating 25 tables, environment-aware migration helper (auto-apply
dev, validate-only prod), DesignTimeDbContextFactory, optimistic concurrency
on DeploymentRecord. 20 tests verify schema, CRUD, relationships, cascades.
This commit is contained in:
Joseph Doherty
2026-03-16 19:15:50 -04:00
parent 9bc5a5163f
commit 1996b21961
23 changed files with 4494 additions and 9 deletions
@@ -0,0 +1,149 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class TemplateConfiguration : IEntityTypeConfiguration<Template>
{
public void Configure(EntityTypeBuilder<Template> builder)
{
builder.HasKey(t => t.Id);
builder.Property(t => t.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(t => t.Description)
.HasMaxLength(2000);
builder.HasIndex(t => t.Name).IsUnique();
// Self-referencing parent template (inheritance)
builder.HasOne<Template>()
.WithMany()
.HasForeignKey(t => t.ParentTemplateId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
builder.HasMany(t => t.Attributes)
.WithOne()
.HasForeignKey(a => a.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Alarms)
.WithOne()
.HasForeignKey(a => a.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Scripts)
.WithOne()
.HasForeignKey(s => s.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Compositions)
.WithOne()
.HasForeignKey(c => c.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
}
}
public class TemplateAttributeConfiguration : IEntityTypeConfiguration<TemplateAttribute>
{
public void Configure(EntityTypeBuilder<TemplateAttribute> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.Value)
.HasMaxLength(4000);
builder.Property(a => a.Description)
.HasMaxLength(2000);
builder.Property(a => a.DataSourceReference)
.HasMaxLength(500);
builder.Property(a => a.DataType)
.HasConversion<string>()
.HasMaxLength(50);
builder.HasIndex(a => new { a.TemplateId, a.Name }).IsUnique();
}
}
public class TemplateAlarmConfiguration : IEntityTypeConfiguration<TemplateAlarm>
{
public void Configure(EntityTypeBuilder<TemplateAlarm> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.Description)
.HasMaxLength(2000);
builder.Property(a => a.TriggerType)
.HasConversion<string>()
.HasMaxLength(50);
builder.Property(a => a.TriggerConfiguration)
.HasMaxLength(4000);
builder.HasIndex(a => new { a.TemplateId, a.Name }).IsUnique();
}
}
public class TemplateScriptConfiguration : IEntityTypeConfiguration<TemplateScript>
{
public void Configure(EntityTypeBuilder<TemplateScript> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(s => s.Code)
.IsRequired();
builder.Property(s => s.TriggerType)
.HasMaxLength(50);
builder.Property(s => s.TriggerConfiguration)
.HasMaxLength(4000);
builder.Property(s => s.ParameterDefinitions)
.HasMaxLength(4000);
builder.Property(s => s.ReturnDefinition)
.HasMaxLength(4000);
builder.HasIndex(s => new { s.TemplateId, s.Name }).IsUnique();
}
}
public class TemplateCompositionConfiguration : IEntityTypeConfiguration<TemplateComposition>
{
public void Configure(EntityTypeBuilder<TemplateComposition> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.InstanceName)
.IsRequired()
.HasMaxLength(200);
// The composed template reference
builder.HasOne<Template>()
.WithMany()
.HasForeignKey(c => c.ComposedTemplateId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(c => new { c.TemplateId, c.InstanceName }).IsUnique();
}
}