Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/DeploymentConfiguration.cs
T
Joseph Doherty 47f3a703bf perf(search): prefix-by-default Deployments and config-audit search; bounded Contains opt-in (ScadaBridge#50)
Deployments free-text search and the config-audit EntityName filter no longer
emit LIKE '%term%' by default. Prefix (LIKE 'term%') is the default; substring
is an explicit opt-in (DeploymentSearchMode.Contains / entityNameContains)
always bounded by a DeployedAt/Timestamp window (explicit, else 30 days).
Migration PrefixSearchIndexes adds DeployedBy, RevisionHash and EntityName
indexes. Deployments page gains a 'Match anywhere' checkbox.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 15:42:56 -05:00

122 lines
4.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Configurations;
public class DeploymentRecordConfiguration : IEntityTypeConfiguration<DeploymentRecord>
{
/// <summary>Configures the EF Core mapping for <see cref="DeploymentRecord"/>.</summary>
/// <param name="builder">The entity type builder.</param>
public void Configure(EntityTypeBuilder<DeploymentRecord> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.DeploymentId)
.IsRequired()
.HasMaxLength(100);
builder.Property(d => d.RevisionHash)
.HasMaxLength(100);
builder.Property(d => d.DeployedBy)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.Status)
.HasConversion<string>()
.HasMaxLength(50);
builder.HasOne<Instance>()
.WithMany()
.HasForeignKey(d => d.InstanceId)
.OnDelete(DeleteBehavior.Restrict);
// Optimistic concurrency on deployment status records
builder.Property<byte[]>("RowVersion")
.IsRowVersion();
builder.HasIndex(d => d.DeploymentId).IsUnique();
builder.HasIndex(d => d.InstanceId);
// Prefix-search legs of the Deployments page free-text search (ScadaBridge#50):
// DeploymentId and Instance.UniqueName are already uniquely indexed.
builder.HasIndex(d => d.DeployedBy).HasDatabaseName("IX_DeploymentRecords_DeployedBy");
builder.HasIndex(d => d.RevisionHash).HasDatabaseName("IX_DeploymentRecords_RevisionHash");
// Deployment-list sort key. Every list read orders DeployedAt DESC, Id DESC
// (DeploymentManagerRepository.QueryDeploymentListPageAsync, which register
// row 37 made the single paged read behind BOTH the Central UI page and the
// CLI/HTTP QueryDeployments command) — Id is the deterministic tie-break, because
// a bulk site deploy stamps many rows in the same DeployedAt instant and a
// non-deterministic order would let a row repeat or vanish across pages.
// Declaring both key columns descending lets a forward scan feed the paged
// Skip/Take directly instead of the server sorting the whole filtered set per
// page (deferred-work register row 35).
//
// This REPLACES the old single-column HasIndex(d => d.DeployedAt): that index
// is strictly redundant here, not merely overlapping. DeployedAt leads both, so
// any predicate the old one served the composite serves too (direction does not
// affect seekability — SQL Server scans either way), and the leaf is the same
// width, since Id is the clustered PK and was therefore already sitting in the
// old index's leaf as the row locator. Keeping it would buy nothing and cost a
// second index maintained on every insert and status transition.
builder.HasIndex(d => new { d.DeployedAt, d.Id })
.IsDescending(true, true)
.HasDatabaseName("IX_DeploymentRecords_DeployedAt_Id");
}
}
public class DeployedConfigSnapshotConfiguration : IEntityTypeConfiguration<DeployedConfigSnapshot>
{
/// <summary>Configures the EF Core mapping for <see cref="DeployedConfigSnapshot"/>.</summary>
/// <param name="builder">The entity type builder.</param>
public void Configure(EntityTypeBuilder<DeployedConfigSnapshot> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.DeploymentId)
.IsRequired()
.HasMaxLength(100);
builder.Property(s => s.RevisionHash)
.IsRequired()
.HasMaxLength(100);
builder.Property(s => s.ConfigurationJson)
.IsRequired();
builder.HasOne<Instance>()
.WithMany()
.HasForeignKey(s => s.InstanceId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(s => s.InstanceId).IsUnique();
builder.HasIndex(s => s.DeploymentId);
}
}
public class SystemArtifactDeploymentRecordConfiguration : IEntityTypeConfiguration<SystemArtifactDeploymentRecord>
{
/// <summary>Configures the EF Core mapping for <see cref="SystemArtifactDeploymentRecord"/>.</summary>
/// <param name="builder">The entity type builder.</param>
public void Configure(EntityTypeBuilder<SystemArtifactDeploymentRecord> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.ArtifactType)
.IsRequired()
.HasMaxLength(100);
builder.Property(d => d.DeployedBy)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.PerSiteStatus)
.HasMaxLength(4000);
builder.HasIndex(d => d.DeployedAt);
}
}