Compare commits
3 Commits
0139c9ca83
...
5615f3d0c7
| Author | SHA1 | Date | |
|---|---|---|---|
| 5615f3d0c7 | |||
| a968cefbc2 | |||
| 68548432b3 |
@@ -0,0 +1,272 @@
|
|||||||
|
# Derive-on-compose template specialization
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Match Aveva System Platform's composition model: composing template
|
||||||
|
`$Sensor` into template `$Pump` no longer references `$Sensor` directly. Instead
|
||||||
|
the system creates a derived template that **inherits** from `$Sensor`, then the
|
||||||
|
composition references the derived template. The derived template lives under
|
||||||
|
the owning parent and can:
|
||||||
|
|
||||||
|
- override attribute default values
|
||||||
|
- override script bodies
|
||||||
|
- add new attributes / scripts the base doesn't have
|
||||||
|
- be prevented from overriding fields the base marks as locked
|
||||||
|
|
||||||
|
This is the user-selected approach (Option C "Always-derive") from the
|
||||||
|
brainstorming session, with all four customization scopes enabled.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
- Per-composition customization is a real SCADA use case (Pump's TempSensor
|
||||||
|
needs different alarm thresholds from Motor's TempSensor).
|
||||||
|
- Single parent always at design time: removes the multi-parent picker we just
|
||||||
|
added.
|
||||||
|
- Industry-standard mental model for users coming from Aveva / Wonderware.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Replacing the existing `ParentTemplateId` inheritance chain — we reuse it.
|
||||||
|
- Versioning of base templates separately from derived (out of scope; can layer
|
||||||
|
later).
|
||||||
|
- Cross-template attribute references (already covered by Children/Parent).
|
||||||
|
|
||||||
|
## Data model changes
|
||||||
|
|
||||||
|
`Template` gains:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public bool IsDerived { get; set; } // hides from main tree
|
||||||
|
public int? OwnerCompositionId { get; set; } // back-ref to composition
|
||||||
|
```
|
||||||
|
|
||||||
|
`TemplateAttribute` gains:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public bool IsInherited { get; set; } // value came from base
|
||||||
|
public bool LockedInDerived { get; set; } // base marks "no override"
|
||||||
|
```
|
||||||
|
|
||||||
|
`TemplateScript` gains the same `IsInherited` / `LockedInDerived` pair.
|
||||||
|
|
||||||
|
`TemplateComposition` is unchanged in shape — `ComposedTemplateId` now points
|
||||||
|
at the **derived** template, not the base. The base is reachable via
|
||||||
|
`derived.ParentTemplateId`.
|
||||||
|
|
||||||
|
**Why a separate `IsDerived` flag rather than just "has a parent and is composed
|
||||||
|
once":** explicit marker keeps the tree-view filtering trivial and signals
|
||||||
|
intent independent of current composition state.
|
||||||
|
|
||||||
|
**Why `OwnerCompositionId` instead of inferring from `TemplateComposition`
|
||||||
|
back-pointers:** O(1) lookup for cascade-delete and forbid-direct-edit paths.
|
||||||
|
|
||||||
|
## Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
Compose "$Sensor" into "$Pump" as instance "TempSensor":
|
||||||
|
1. Create new template { Name: "Pump.TempSensor", ParentTemplateId: $Sensor.Id,
|
||||||
|
IsDerived: true, Description: from $Sensor }
|
||||||
|
2. Copy $Sensor.Attributes into the new template marked IsInherited=true
|
||||||
|
3. Copy $Sensor.Scripts into the new template marked IsInherited=true
|
||||||
|
4. Create TemplateComposition { TemplateId: $Pump.Id,
|
||||||
|
ComposedTemplateId: newTemplate.Id,
|
||||||
|
InstanceName: "TempSensor" }
|
||||||
|
5. Set newTemplate.OwnerCompositionId = the new composition's Id
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete composition or owning parent → cascade-delete the derived template.
|
||||||
|
|
||||||
|
Rename composition InstanceName → rename the derived template (`Pump.NewName`).
|
||||||
|
|
||||||
|
Edit base attribute that is `IsInherited=true` on derivatives → the derivatives
|
||||||
|
pick up the change *if* they haven't overridden that field. Override sets
|
||||||
|
`IsInherited=false`.
|
||||||
|
|
||||||
|
## Lock semantics
|
||||||
|
|
||||||
|
Existing `IsLocked` on `TemplateAttribute` already exists with the meaning
|
||||||
|
"this attribute on this template is locked for editing." Add a second flag
|
||||||
|
`LockedInDerived` meaning "derived templates may not override the value
|
||||||
|
inherited from this attribute." These compose:
|
||||||
|
|
||||||
|
| State on base | What derived can do |
|
||||||
|
|---|---|
|
||||||
|
| neither flag set | Override value freely |
|
||||||
|
| `LockedInDerived` only | Cannot override; inherited value is final |
|
||||||
|
| `IsLocked` only | Base itself can't be edited; derived can still override |
|
||||||
|
| both | Locked everywhere |
|
||||||
|
|
||||||
|
## Flattening implications
|
||||||
|
|
||||||
|
`FlatteningService.ResolveInheritedScripts` already walks a template chain via
|
||||||
|
`ParentTemplateId`. That logic already handles "child overrides parent;
|
||||||
|
parent's `IsLocked` blocks override." We extend the same with
|
||||||
|
`LockedInDerived` for both attributes and scripts.
|
||||||
|
|
||||||
|
`ResolveComposedScripts` walks compositions → composed templates. Today the
|
||||||
|
prefix is the `InstanceName`. With derived templates the prefix is still the
|
||||||
|
`InstanceName` (the derived template's name `Pump.TempSensor` doesn't show up
|
||||||
|
in canonical paths — paths use the slot name, not the template name).
|
||||||
|
|
||||||
|
The `ResolvedScript.Scope` we landed for Phase 2 of the previous design still
|
||||||
|
applies: `SelfPath = "TempSensor"`, `ParentPath = ""`. No change.
|
||||||
|
|
||||||
|
## UI changes
|
||||||
|
|
||||||
|
### Template tree
|
||||||
|
|
||||||
|
Hide `IsDerived` templates from the main list. They're reachable via:
|
||||||
|
- the Compositions tab on the parent template (click the row → opens the
|
||||||
|
derived template's edit page)
|
||||||
|
- a "Show derived templates" toggle on the tree page (off by default)
|
||||||
|
|
||||||
|
### TemplateEdit for a derived template
|
||||||
|
|
||||||
|
Top banner: *"Derived from `$Sensor` — composed inside `$Pump` as `TempSensor`."*
|
||||||
|
|
||||||
|
Attributes table renders three columns of state:
|
||||||
|
- **Override / Inherited** badge per row
|
||||||
|
- Locked-from-base attributes render readonly with a 🔒 icon and tooltip
|
||||||
|
*"Locked by base — cannot override."*
|
||||||
|
|
||||||
|
Scripts table same treatment.
|
||||||
|
|
||||||
|
Adding a new attribute or script on the derived template is allowed (creates
|
||||||
|
a row with `IsInherited = false`).
|
||||||
|
|
||||||
|
Removing an inherited row reverts it to the base value (the row goes back to
|
||||||
|
inherited state). Removing an own-added row deletes it.
|
||||||
|
|
||||||
|
### TemplateEdit for a base template
|
||||||
|
|
||||||
|
Two extra columns on attribute / script tables:
|
||||||
|
- 🔒 toggle for `LockedInDerived` — "Lock this against per-slot override"
|
||||||
|
|
||||||
|
### Compositions tab
|
||||||
|
|
||||||
|
Today: lists composition rows with InstanceName + ComposedTemplate name.
|
||||||
|
After: each row links to *its derived template* (not the base). InstanceName
|
||||||
|
becomes the visible label.
|
||||||
|
|
||||||
|
Renaming a composition renames the derived template too.
|
||||||
|
|
||||||
|
### Composition picker (when adding a composition)
|
||||||
|
|
||||||
|
Today: pick a template + provide an instance name.
|
||||||
|
After: pick a **base** template + provide an instance name. The system creates
|
||||||
|
the derived template behind the scenes.
|
||||||
|
|
||||||
|
The picker filters out `IsDerived` templates — you can only compose bases.
|
||||||
|
|
||||||
|
## Editor metadata implications
|
||||||
|
|
||||||
|
The multi-parent picker becomes mostly irrelevant:
|
||||||
|
|
||||||
|
- **Derived template**: always single parent (the composition it's owned by).
|
||||||
|
`Parent.*` resolves to that one. No picker.
|
||||||
|
- **Base template**: still has no direct parent (it's a library entry).
|
||||||
|
`Parent.*` autocompletion is suppressed. Scripts on bases that use
|
||||||
|
`Parent.*` get a warning *"Parent access on a base template is ambiguous —
|
||||||
|
override this script in the derived template instead."*
|
||||||
|
|
||||||
|
`TemplateEdit.BuildParentContextsAsync` simplifies to: "if derived, return the
|
||||||
|
single owning parent; else return null."
|
||||||
|
|
||||||
|
`GetTemplatesComposingAsync` repository method still useful (e.g., for "find
|
||||||
|
all uses of this base"), but the editor metadata path doesn't need it.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
One-shot for existing data:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- pseudo-SQL describing intent
|
||||||
|
FOREACH composition IN TemplateComposition:
|
||||||
|
derived := INSERT INTO Templates (
|
||||||
|
Name = parent.Name + "." + composition.InstanceName,
|
||||||
|
ParentTemplateId = composition.ComposedTemplateId,
|
||||||
|
IsDerived = true,
|
||||||
|
OwnerCompositionId = composition.Id
|
||||||
|
)
|
||||||
|
-- Copy attributes from base, mark IsInherited=true
|
||||||
|
INSERT INTO TemplateAttributes
|
||||||
|
SELECT @derived.Id, Name, Value, DataType, true, ... FROM base.Attributes
|
||||||
|
-- Same for scripts
|
||||||
|
UPDATE TemplateComposition SET ComposedTemplateId = derived.Id WHERE Id = composition.Id
|
||||||
|
```
|
||||||
|
|
||||||
|
EF Core migration in `ScadaLink.ConfigurationDatabase/Migrations/`.
|
||||||
|
|
||||||
|
Rollback strategy: the migration is one-way for new derivations, but old
|
||||||
|
composition data can be reconstructed from `IsDerived` templates' `ParentTemplateId`.
|
||||||
|
|
||||||
|
## Phased rollout
|
||||||
|
|
||||||
|
Each phase is independently shippable and reviewable.
|
||||||
|
|
||||||
|
1. **Schema + entities.** Add the new fields. Empty migration. EF mappings.
|
||||||
|
No behavior changes. Existing data unaffected.
|
||||||
|
|
||||||
|
2. **Composition flow change.** Modify `TemplateService.AddCompositionAsync`
|
||||||
|
to derive on compose for *new* compositions. Existing data still has direct
|
||||||
|
compositions and continues to work. Two modes coexist during the cutover.
|
||||||
|
|
||||||
|
3. **Migration.** EF Core migration script that walks existing compositions
|
||||||
|
and creates the derived templates retroactively. After this all
|
||||||
|
compositions are derived.
|
||||||
|
|
||||||
|
4. **Inherit/override resolution.** Update `FlatteningService` to merge
|
||||||
|
inherited and overridden fields. Tests for the override semantics.
|
||||||
|
|
||||||
|
5. **Lock semantics.** Wire `LockedInDerived` through `TemplateService`
|
||||||
|
update paths. Tests.
|
||||||
|
|
||||||
|
6. **Template tree UI.** Hide derived templates from the main listing;
|
||||||
|
surface them through the parent's Compositions tab.
|
||||||
|
|
||||||
|
7. **Derived TemplateEdit UI.** Banner, inherited/override badges,
|
||||||
|
readonly-when-locked, override/revert actions.
|
||||||
|
|
||||||
|
8. **Base TemplateEdit UI.** Add the LockedInDerived toggle column.
|
||||||
|
|
||||||
|
9. **Editor metadata simplification.** Replace the multi-parent picker with
|
||||||
|
the single-parent resolver. Base templates suppress `Parent.*` assistance
|
||||||
|
and warn on use.
|
||||||
|
|
||||||
|
## Out of scope (for now)
|
||||||
|
|
||||||
|
- Versioning of base templates with explicit "update derived templates to
|
||||||
|
base v2" workflow.
|
||||||
|
- Reverse-flow: editing a derived value and asking "promote to base."
|
||||||
|
- Multiple inheritance levels for derivation (e.g., `$Sensor → $Sensor.Pump →
|
||||||
|
$Sensor.Pump.HighTemp`) — the data model supports it via
|
||||||
|
`ParentTemplateId`, but the UX hasn't been designed.
|
||||||
|
- Cross-tenant template libraries.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **Naming**: dot-separated (`Pump.TempSensor`). Matches the canonical-path
|
||||||
|
format used in flattening. Visible in audit logs / error messages.
|
||||||
|
- **Delete base with derivatives**: block the delete and list the derivatives.
|
||||||
|
User must remove or repoint them first.
|
||||||
|
- **Migration of existing data**: EF Core migration on next startup
|
||||||
|
auto-derives every existing composition. After deploy all compositions are
|
||||||
|
derived; no mixed-mode code paths.
|
||||||
|
- **Tree UX**: derived templates hidden by default. "Show derived templates"
|
||||||
|
toggle on the tree page reveals them indented under their base. Always
|
||||||
|
reachable from the parent's Compositions tab.
|
||||||
|
|
||||||
|
## Confirmed semantics
|
||||||
|
|
||||||
|
- **Re-composing the same base on the same parent in two slots** (e.g. Pump
|
||||||
|
composes Sensor twice as `IntakeSensor` and `OutletSensor`) produces two
|
||||||
|
derived templates: `Pump.IntakeSensor` and `Pump.OutletSensor`, both
|
||||||
|
inheriting from `Sensor`.
|
||||||
|
|
||||||
|
- **Inheritance updates flow downward**: if a base attribute changes value
|
||||||
|
later and the derivative has `IsInherited = true` for that attribute, the
|
||||||
|
derived value updates. Once overridden (`IsInherited = false`), changes to
|
||||||
|
the base no longer affect that field.
|
||||||
|
|
||||||
|
- **Subsequent `LockedInDerived` after overrides exist**: surface as a
|
||||||
|
validation error at deploy time; do not force-revert silently.
|
||||||
@@ -12,6 +12,21 @@ public class Template
|
|||||||
public ICollection<TemplateScript> Scripts { get; set; } = new List<TemplateScript>();
|
public ICollection<TemplateScript> Scripts { get; set; } = new List<TemplateScript>();
|
||||||
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
|
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when this template was auto-derived to back a TemplateComposition
|
||||||
|
/// slot. Derived templates inherit from a base (see <see cref="ParentTemplateId"/>),
|
||||||
|
/// are owned by their composition row (see <see cref="OwnerCompositionId"/>),
|
||||||
|
/// and are hidden from the main template tree by default.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDerived { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Back-reference to the <see cref="TemplateComposition"/> that owns this
|
||||||
|
/// derived template. Non-null only when <see cref="IsDerived"/>; cascade-
|
||||||
|
/// delete when the composition is removed. Always null on base templates.
|
||||||
|
/// </summary>
|
||||||
|
public int? OwnerCompositionId { get; set; }
|
||||||
|
|
||||||
public Template(string name)
|
public Template(string name)
|
||||||
{
|
{
|
||||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
|||||||
@@ -13,6 +13,21 @@ public class TemplateAttribute
|
|||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public string? DataSourceReference { get; set; }
|
public string? DataSourceReference { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when this row was copied from the base template and has not been
|
||||||
|
/// overridden on the derived template. Changes to the base flow downward
|
||||||
|
/// for inherited rows; an explicit override flips this to false.
|
||||||
|
/// Always false on base (non-derived) templates.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInherited { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set on a base attribute. When true, derived templates may not override
|
||||||
|
/// the value — the row is rendered readonly with a 🔒 in the derived UI,
|
||||||
|
/// and any attempt to update it through the API is rejected.
|
||||||
|
/// </summary>
|
||||||
|
public bool LockedInDerived { get; set; }
|
||||||
|
|
||||||
public TemplateAttribute(string name)
|
public TemplateAttribute(string name)
|
||||||
{
|
{
|
||||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
|||||||
@@ -13,6 +13,21 @@ public class TemplateScript
|
|||||||
public string? ReturnDefinition { get; set; }
|
public string? ReturnDefinition { get; set; }
|
||||||
public TimeSpan? MinTimeBetweenRuns { get; set; }
|
public TimeSpan? MinTimeBetweenRuns { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when this row was copied from the base template and has not been
|
||||||
|
/// overridden on the derived template. Changes to the base flow downward
|
||||||
|
/// for inherited rows; an explicit override flips this to false.
|
||||||
|
/// Always false on base (non-derived) templates.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInherited { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set on a base script. When true, derived templates may not override
|
||||||
|
/// the script body — the row is rendered readonly with a 🔒 in the derived
|
||||||
|
/// UI, and any attempt to update it through the API is rejected.
|
||||||
|
/// </summary>
|
||||||
|
public bool LockedInDerived { get; set; }
|
||||||
|
|
||||||
public TemplateScript(string name, string code)
|
public TemplateScript(string name, string code)
|
||||||
{
|
{
|
||||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||||
|
|||||||
Generated
+1300
File diff suppressed because it is too large
Load Diff
+83
@@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDerivedTemplateFields : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsInherited",
|
||||||
|
table: "TemplateScripts",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "LockedInDerived",
|
||||||
|
table: "TemplateScripts",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsDerived",
|
||||||
|
table: "Templates",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "OwnerCompositionId",
|
||||||
|
table: "Templates",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsInherited",
|
||||||
|
table: "TemplateAttributes",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "LockedInDerived",
|
||||||
|
table: "TemplateAttributes",
|
||||||
|
type: "bit",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsInherited",
|
||||||
|
table: "TemplateScripts");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LockedInDerived",
|
||||||
|
table: "TemplateScripts");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsDerived",
|
||||||
|
table: "Templates");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "OwnerCompositionId",
|
||||||
|
table: "Templates");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsInherited",
|
||||||
|
table: "TemplateAttributes");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LockedInDerived",
|
||||||
|
table: "TemplateAttributes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -848,11 +848,17 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
|||||||
b.Property<int?>("FolderId")
|
b.Property<int?>("FolderId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDerived")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(200)
|
.HasMaxLength(200)
|
||||||
.HasColumnType("nvarchar(200)");
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
|
b.Property<int?>("OwnerCompositionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int?>("ParentTemplateId")
|
b.Property<int?>("ParentTemplateId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -935,9 +941,15 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
|||||||
.HasMaxLength(2000)
|
.HasMaxLength(2000)
|
||||||
.HasColumnType("nvarchar(2000)");
|
.HasColumnType("nvarchar(2000)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsInherited")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<bool>("IsLocked")
|
b.Property<bool>("IsLocked")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("LockedInDerived")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(200)
|
.HasMaxLength(200)
|
||||||
@@ -1027,9 +1039,15 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsInherited")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<bool>("IsLocked")
|
b.Property<bool>("IsLocked")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("LockedInDerived")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<TimeSpan?>("MinTimeBetweenRuns")
|
b.Property<TimeSpan?>("MinTimeBetweenRuns")
|
||||||
.HasColumnType("time");
|
.HasColumnType("time");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user