feat(configdb): add RowVersion to live-edit entities
Phase 1a of the v2 entity-model rewrite. Adds:
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
and the EF Core mapping
e.Property(x => x.RowVersion).IsRowVersion();
to 12 live-edit entities:
Equipment, DriverInstance, Device, Tag, PollGroup, Namespace,
UnsArea, UnsLine, NodeAcl, Script, VirtualTag, ScriptedAlarm
These are the entities that v2 admins will edit directly via
AdminOperationsActor (no draft staging). RowVersion enables
last-write-wins detection when two operators race on the same row.
GenerationId FKs are still in place on these entities (removed in Task 14b);
this commit only adds the rowversion column so the migration in Task 14f can
emit ADD COLUMN before DROP FK as a single atomic step.
This commit is contained in:
@@ -19,5 +19,8 @@ public sealed class Device
|
||||
/// <summary>Schemaless per-driver-type device config (host, port, unit ID, slot, etc.).</summary>
|
||||
public required string DeviceConfig { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ public sealed class DriverInstance
|
||||
/// </summary>
|
||||
public string? ResilienceConfig { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -60,5 +60,8 @@ public sealed class Equipment
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ public sealed class Namespace
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -28,5 +28,8 @@ public sealed class NodeAcl
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -15,5 +15,8 @@ public sealed class PollGroup
|
||||
|
||||
public int IntervalMs { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -34,5 +34,8 @@ public sealed class Script
|
||||
/// <summary>Language — always "CSharp" today; placeholder for future engines (Python/Lua).</summary>
|
||||
public string Language { get; set; } = "CSharp";
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -55,5 +55,8 @@ public sealed class ScriptedAlarm
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@ public sealed class Tag
|
||||
/// <summary>Register address / scaling / poll group / byte-order / etc. — schemaless per driver type.</summary>
|
||||
public required string TagConfig { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ public sealed class UnsArea
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
public ServerCluster? Cluster { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,5 +17,8 @@ public sealed class UnsLine
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -49,5 +49,8 @@ public sealed class VirtualTag
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>Optimistic concurrency token for last-write-wins detection in the v2 live-edit model.</summary>
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
|
||||
public ConfigGeneration? Generation { get; set; }
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.Kind).HasConversion<string>().HasMaxLength(32);
|
||||
e.Property(x => x.NamespaceUri).HasMaxLength(256);
|
||||
e.Property(x => x.Notes).HasMaxLength(1024);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany()
|
||||
.HasForeignKey(x => x.GenerationId)
|
||||
@@ -239,6 +240,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.ClusterId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(32);
|
||||
e.Property(x => x.Notes).HasMaxLength(512);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasOne(x => x.Cluster).WithMany().HasForeignKey(x => x.ClusterId).OnDelete(DeleteBehavior.Restrict);
|
||||
@@ -260,6 +262,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.UnsAreaId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(32);
|
||||
e.Property(x => x.Notes).HasMaxLength(512);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -289,6 +292,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.DriverType).HasMaxLength(32);
|
||||
e.Property(x => x.DriverConfig).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.ResilienceConfig).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
e.HasOne(x => x.Cluster).WithMany().HasForeignKey(x => x.ClusterId).OnDelete(DeleteBehavior.Restrict);
|
||||
@@ -313,6 +317,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.DeviceConfig).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -345,6 +350,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.ManufacturerUri).HasMaxLength(512);
|
||||
e.Property(x => x.DeviceManualUri).HasMaxLength(512);
|
||||
e.Property(x => x.EquipmentClassRef).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -379,6 +385,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.AccessLevel).HasConversion<string>().HasMaxLength(16);
|
||||
e.Property(x => x.PollGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.TagConfig).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -409,6 +416,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.PollGroupId).HasMaxLength(64);
|
||||
e.Property(x => x.DriverInstanceId).HasMaxLength(64);
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -431,6 +439,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.ScopeId).HasMaxLength(64);
|
||||
e.Property(x => x.PermissionFlags).HasConversion<int>();
|
||||
e.Property(x => x.Notes).HasMaxLength(512);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -655,6 +664,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.SourceCode).HasColumnType("nvarchar(max)");
|
||||
e.Property(x => x.SourceHash).HasMaxLength(64);
|
||||
e.Property(x => x.Language).HasMaxLength(16);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -681,6 +691,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.Name).HasMaxLength(128);
|
||||
e.Property(x => x.DataType).HasMaxLength(32);
|
||||
e.Property(x => x.ScriptId).HasMaxLength(64);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
@@ -708,6 +719,7 @@ public sealed class OtOpcUaConfigDbContext(DbContextOptions<OtOpcUaConfigDbConte
|
||||
e.Property(x => x.AlarmType).HasMaxLength(32);
|
||||
e.Property(x => x.MessageTemplate).HasMaxLength(1024);
|
||||
e.Property(x => x.PredicateScriptId).HasMaxLength(64);
|
||||
e.Property(x => x.RowVersion).IsRowVersion();
|
||||
|
||||
e.HasOne(x => x.Generation).WithMany().HasForeignKey(x => x.GenerationId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user