using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications; namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Configurations; /// /// EF Core mapping for the central notification outbox entity. /// and are intentionally left unconstrained /// (nullable nvarchar(max)) as they carry variable-length JSON / target snapshots. /// public class NotificationOutboxConfiguration : IEntityTypeConfiguration { /// Configures the EF Core entity type mapping for . /// The entity type builder to configure. public void Configure(EntityTypeBuilder builder) { builder.HasKey(n => n.NotificationId); builder.Property(n => n.NotificationId).HasMaxLength(64); builder.Property(n => n.Type) .HasConversion() .HasMaxLength(32) .IsRequired(); builder.Property(n => n.Status) .HasConversion() .HasMaxLength(32) .IsRequired(); builder.Property(n => n.ListName) .HasMaxLength(200) .IsRequired(); builder.Property(n => n.Subject) .HasMaxLength(1000) .IsRequired(); builder.Property(n => n.Body).IsRequired(); builder.Property(n => n.LastError).HasMaxLength(4000); builder.Property(n => n.SourceSiteId) .HasMaxLength(100) .IsRequired(); builder.Property(n => n.SourceInstanceId).HasMaxLength(200); builder.Property(n => n.SourceScript).HasMaxLength(200); // SourceNode: node-local identifier of the // cluster member that produced the notification (e.g. "node-a", "central-a"). // NULL is valid for rows that pre-date this feature. ASCII — varchar(64). // No index — KPIs are per-site on this table, not per-node; SourceNode is only // echoed onto NotifyDeliver audit rows for cross-row correlation. builder.Property(n => n.SourceNode) .HasColumnType("varchar(64)") .HasMaxLength(64) .IsUnicode(false); // OriginExecutionId: nullable uniqueidentifier carried from the // site so the dispatcher can echo it onto NotifyDeliver audit rows. No index — // it is never a query predicate on this table, only copied onto audit events. // OriginParentExecutionId: nullable uniqueidentifier carried from // the site — the routed run's parent ExecutionId — so the dispatcher can echo it // onto NotifyDeliver audit rows. No index — same rationale as OriginExecutionId. builder.HasIndex(n => new { n.Status, n.NextAttemptAt }); builder.HasIndex(n => new { n.SourceSiteId, n.CreatedAt }); // Covers ComputeKpisAsync's "DeliveredLastInterval" count // (Status == Delivered && DeliveredAt >= deliveredSince): filtered to just the // Delivered rows so the index stays small relative to the full table and the KPI // query can seek it directly instead of scanning every status (arch-review WP1.4). // Status is stored as its enum name (HasConversion() above), so the filter // predicate matches on the literal 'Delivered', not the underlying int value. // No N'' prefix: the value is ASCII-only (fine on SQL Server's nvarchar column) // and the repository tests' SQLite provider rejects the T-SQL N'' national-string // literal syntax outright when EnsureCreated() runs this filter as DDL. builder.HasIndex(n => n.DeliveredAt) .HasDatabaseName("IX_Notifications_Delivered") .HasFilter("[Status] = 'Delivered'"); } }