406 lines
26 KiB
C#
406 lines
26 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Deployment;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
|
|
public interface IDeploymentManagerRepository
|
|
{
|
|
// DeploymentRecord
|
|
/// <summary>
|
|
/// Gets a deployment record by its ID.
|
|
/// </summary>
|
|
/// <param name="id">The deployment record ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The deployment record, or null if not found.</returns>
|
|
Task<DeploymentRecord?> GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Database-side paged, filtered and instance-joined deployment listing — THE
|
|
/// paged read of <c>DeploymentRecords</c>, shared by the Central UI
|
|
/// deployment-status page and the CLI/HTTP <c>QueryDeployments</c> command.
|
|
///
|
|
/// <para>
|
|
/// It was two reads until deferred-work register row 37. The other one
|
|
/// (<c>QueryDeploymentSummariesAsync</c>) differed in three ways that were all
|
|
/// defects rather than requirements: it took a raw <c>skip</c> instead of a page
|
|
/// number, returned no total (so a truncated page was indistinguishable from a
|
|
/// complete one), skipped the <c>Instance</c> join, and scoped by a caller-resolved
|
|
/// INSTANCE-ID LIST — which its one caller built by loading every instance in the
|
|
/// system on every scoped query. Site scoping is expressed here as
|
|
/// <see cref="Types.Deployment.DeploymentListFilter.SiteIdScope"/> and resolved by
|
|
/// the join, so no caller needs that read.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Replaces a <c>GetAllDeploymentRecords</c> whole-table read. That method's only
|
|
/// caller loaded EVERY deployment record (the table is insert-only, one row per
|
|
/// deploy attempt for the retention window) plus EVERY instance, then filtered by
|
|
/// site scope, sorted, counted the status tiles and sliced a 25-row page — all in
|
|
/// the Blazor circuit's memory, on every render AND on every deployment-status
|
|
/// push. Cost scaled with the age of the system rather than the size of the page.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>OFFSET paging, not keyset — deliberately.</b> The sibling Audit Log and Site
|
|
/// Calls read paths are keyset-cursored, but this one is offset-paged like the
|
|
/// Notification Outbox, and for the same reason: its contract surfaces a PAGE
|
|
/// NUMBER and a TOTAL COUNT (the page renders a numbered, jump-to-any-page pager),
|
|
/// and a keyset cursor can express neither. The total is needed regardless, since
|
|
/// the status tiles are an aggregate over the same filtered set. The deep-offset
|
|
/// cost that pushes high-volume tables to keyset is bounded here by
|
|
/// <see cref="PurgeTerminalDeploymentRecordsAsync"/>, which caps the table at the
|
|
/// terminal-record retention window — unlike the 365-day central
|
|
/// <c>AuditLog</c>.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Ordering is <c>DeployedAt DESC, Id DESC</c>. The <c>Id</c> tie-break is not
|
|
/// cosmetic: <c>DeployedAt</c> ties on rapid redeploys, and an unstable sort key
|
|
/// makes offset paging non-deterministic — rows repeat across pages or vanish
|
|
/// between them.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="filter">Status / search / site-scope / instance constraints; all applied DB-side.</param>
|
|
/// <param name="pageNumber">1-based page number; values below 1 are floored at 1.</param>
|
|
/// <param name="pageSize">Rows per page; clamped to <c>[1, DeploymentListPage.MaxPageSize]</c>. There is no "unlimited" value.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The requested page of rows plus the total count of the filtered set.</returns>
|
|
Task<Types.Deployment.DeploymentListPage> QueryDeploymentListPageAsync(
|
|
Types.Deployment.DeploymentListFilter filter,
|
|
int pageNumber,
|
|
int pageSize,
|
|
CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// One grouped aggregation returning the per-status row counts of the filtered
|
|
/// set — the server-side replacement for the deployment page's four
|
|
/// <c>records.Count(r => r.Status == X)</c> tile computations over a
|
|
/// client-materialized table.
|
|
///
|
|
/// <para>
|
|
/// <b><see cref="Types.Deployment.DeploymentListFilter.Status"/> is ignored here.</b>
|
|
/// The tiles are the status BREAKDOWN of the otherwise-identically-filtered set,
|
|
/// so constraining them by the active status filter would collapse three of the
|
|
/// four tiles to zero the moment an operator clicked one. Every other dimension
|
|
/// (search, site scope, instance) IS applied, so the tiles always describe the
|
|
/// same population the list is drawn from.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="filter">The active list filter; its <c>Status</c> member is deliberately not applied.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>Per-status counts, with absent statuses reported as 0.</returns>
|
|
Task<Types.Deployment.DeploymentStatusCounts> GetDeploymentStatusCountsAsync(
|
|
Types.Deployment.DeploymentListFilter filter,
|
|
CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes TERMINAL deployment records (<see cref="DeploymentStatus.Success"/> /
|
|
/// <see cref="DeploymentStatus.Failed"/>) that completed before
|
|
/// <paramref name="cutoffUtc"/>, in one bounded batch.
|
|
///
|
|
/// <para>
|
|
/// Non-terminal rows are never touched: an <c>InProgress</c> record is what the
|
|
/// query-before-redeploy reconciliation path reads to decide whether a prior
|
|
/// deploy actually landed at the site, so purging one by age would silently
|
|
/// disable that idempotency guard.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="cutoffUtc">Records completing before this instant are eligible.</param>
|
|
/// <param name="batchSize">Maximum rows deleted in this call.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The number of rows deleted; fewer than <paramref name="batchSize"/> means the sweep is complete.</returns>
|
|
Task<int> PurgeTerminalDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets all deployment records for a specific instance.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A read-only list of deployment records for the instance.</returns>
|
|
Task<IReadOnlyList<DeploymentRecord>> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets the current deployment status for an instance.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The current deployment record, or null if no deployment exists.</returns>
|
|
Task<DeploymentRecord?> GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets a deployment record by deployment ID.
|
|
/// </summary>
|
|
/// <param name="deploymentId">The deployment ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The deployment record, or null if not found.</returns>
|
|
Task<DeploymentRecord?> GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Adds a new deployment record.
|
|
/// </summary>
|
|
/// <param name="record">The deployment record to add.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Updates an existing deployment record.
|
|
/// </summary>
|
|
/// <param name="record">The deployment record to update.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes a deployment record by ID, enforcing optimistic concurrency against the
|
|
/// supplied <paramref name="expectedRowVersion"/>. The caller MUST pass the
|
|
/// <c>RowVersion</c> it last observed on the record so EF emits
|
|
/// <c>DELETE ... WHERE Id = @id AND RowVersion = @prior</c>. A concurrent edit
|
|
/// surfaces as <see cref="Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException"/>
|
|
/// on <see cref="SaveChangesAsync(CancellationToken)"/>, matching the documented
|
|
/// "Optimistic concurrency is used on deployment status records" design rule.
|
|
/// </summary>
|
|
/// <param name="id">The deployment record ID to delete.</param>
|
|
/// <param name="expectedRowVersion">The RowVersion the caller observed; used as the optimistic-concurrency token.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task DeleteDeploymentRecordAsync(int id, byte[] expectedRowVersion, CancellationToken cancellationToken = default);
|
|
|
|
// SystemArtifactDeploymentRecord
|
|
/// <summary>
|
|
/// Gets a system artifact deployment record by ID.
|
|
/// </summary>
|
|
/// <param name="id">The system artifact deployment record ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The system artifact deployment record, or null if not found.</returns>
|
|
Task<SystemArtifactDeploymentRecord?> GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets all system artifact deployment records.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A read-only list of all system artifact deployment records.</returns>
|
|
Task<IReadOnlyList<SystemArtifactDeploymentRecord>> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Adds a new system artifact deployment record.
|
|
/// </summary>
|
|
/// <param name="record">The system artifact deployment record to add.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Updates an existing system artifact deployment record.
|
|
/// </summary>
|
|
/// <param name="record">The system artifact deployment record to update.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes a system artifact deployment record by ID.
|
|
/// </summary>
|
|
/// <param name="id">The system artifact deployment record ID to delete.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes system artifact deployment records deployed before <paramref name="cutoffUtc"/>,
|
|
/// in one bounded batch. One row is written per <i>Deploy Artifacts</i> run and nothing
|
|
/// reads them back, so they share the terminal deployment-record retention window
|
|
/// instead of growing without bound.
|
|
/// </summary>
|
|
/// <param name="cutoffUtc">Records deployed before this instant are eligible.</param>
|
|
/// <param name="batchSize">Maximum rows deleted in this call.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The number of rows deleted; fewer than <paramref name="batchSize"/> means the sweep is complete.</returns>
|
|
Task<int> PurgeSystemArtifactDeploymentRecordsAsync(DateTimeOffset cutoffUtc, int batchSize, CancellationToken cancellationToken = default);
|
|
|
|
// DeployedConfigSnapshot
|
|
/// <summary>
|
|
/// Gets the deployed config snapshot for an instance.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The deployed config snapshot, or null if not found.</returns>
|
|
Task<DeployedConfigSnapshot?> GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// InstanceId → RevisionHash for every <c>DeployedConfigSnapshot</c>. A projection read — never
|
|
/// materialises <c>ConfigurationJson</c>, so it is safe to call for the whole fleet (the
|
|
/// pending-updates aggregate does, once per cache TTL).
|
|
///
|
|
/// <para>
|
|
/// Deliberately UNFILTERED: it always returns the whole fleet's two-column projection, with no
|
|
/// site, state or scope narrowing. A site-scoped or otherwise partial caller must filter the
|
|
/// returned map itself rather than assume the read was narrowed for it.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A dictionary keyed by instance ID; instances with no snapshot are absent.</returns>
|
|
Task<IReadOnlyDictionary<int, string>> GetDeployedRevisionHashesAsync(CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Adds a new deployed config snapshot.
|
|
/// </summary>
|
|
/// <param name="snapshot">The deployed config snapshot to add.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Updates an existing deployed config snapshot.
|
|
/// </summary>
|
|
/// <param name="snapshot">The deployed config snapshot to update.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes the deployed config snapshot for an instance.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
|
|
// Notify-and-fetch: PendingDeployment staging store
|
|
/// <summary>
|
|
/// Stages a flattened instance config for an in-flight deployment. Supersession:
|
|
/// any prior pending row for the same <c>InstanceId</c> is removed first, so at most
|
|
/// one pending row exists per instance (safe because the per-instance operation lock
|
|
/// serializes same-instance deploys). Does NOT call <see cref="SaveChangesAsync"/> —
|
|
/// the caller commits, mirroring the other Add/Delete repository methods.
|
|
/// </summary>
|
|
/// <param name="pending">The pending deployment to stage.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task AddPendingDeploymentAsync(PendingDeployment pending, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets a pending deployment by its deployment ID (the fetch key).
|
|
/// </summary>
|
|
/// <param name="deploymentId">The deployment ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The pending deployment, or null if not found.</returns>
|
|
Task<PendingDeployment?> GetPendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets the pending deployment staged for a given instance (there is at most one per instance
|
|
/// by design — <see cref="AddPendingDeploymentAsync"/> supersedes and
|
|
/// <see cref="StagePendingIfAbsentAsync"/> inserts only if absent). Used by startup reconcile:
|
|
/// when <see cref="StagePendingIfAbsentAsync"/> reports a row already exists (a concurrent
|
|
/// reconcile from the other node, or an in-flight deploy), the handler reads that existing row
|
|
/// so a second concurrently-missing node can fetch the same pending config with its (multi-use,
|
|
/// TTL-bound) token instead of being omitted from the gap. Defensive against >1 row: returns
|
|
/// the most recently created one. When <paramref name="nowUtc"/> is supplied, EXPIRED rows
|
|
/// (<c>ExpiresAtUtc <= nowUtc</c>) are filtered out so the caller never receives a token the
|
|
/// config-fetch endpoint would 404.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance whose pending deployment to read.</param>
|
|
/// <param name="nowUtc">When supplied, only rows with <c>ExpiresAtUtc > nowUtc</c> are considered (expired rows excluded).</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The pending deployment for the instance, or null if none is staged.</returns>
|
|
Task<PendingDeployment?> GetPendingDeploymentByInstanceIdAsync(int instanceId, DateTimeOffset? nowUtc = null, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Deletes a pending deployment by its deployment ID. No-op if not found. Does NOT
|
|
/// call <see cref="SaveChangesAsync"/> — the caller commits, mirroring the other
|
|
/// Add/Delete repository methods.
|
|
/// </summary>
|
|
/// <param name="deploymentId">The deployment ID to delete.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task DeletePendingDeploymentByIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Purges all pending deployments whose <c>ExpiresAtUtc</c> is at or before
|
|
/// <paramref name="nowUtc"/> (TTL maintenance). This is a self-contained maintenance
|
|
/// operation: it commits its own delete and returns the number of rows removed.
|
|
/// </summary>
|
|
/// <param name="nowUtc">The current UTC time; rows with <c>ExpiresAtUtc <= nowUtc</c> are purged.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The number of pending deployments purged.</returns>
|
|
Task<int> PurgeExpiredPendingDeploymentsAsync(DateTimeOffset nowUtc, CancellationToken cancellationToken = default);
|
|
|
|
// Startup reconciliation: expected-set query and insert-if-absent staging
|
|
|
|
/// <summary>
|
|
/// Returns the set of instances that central considers deployed for the given site — the join of
|
|
/// <c>DeployedConfigSnapshot</c> (by InstanceId) with <c>Instance</c> (where SiteId == siteId).
|
|
/// Instances without a snapshot are excluded. No <c>ConfigurationJson</c> is loaded; the full
|
|
/// config is fetched on demand via the HTTP endpoint using a freshly-minted token from
|
|
/// <see cref="StagePendingIfAbsentAsync"/>.
|
|
/// </summary>
|
|
/// <param name="siteId">The site primary key to filter on.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>All deployed instances for the site, as lightweight <see cref="ExpectedDeployment"/> projections.</returns>
|
|
Task<IReadOnlyList<ExpectedDeployment>> GetExpectedDeploymentsForSiteAsync(int siteId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Inserts a <see cref="PendingDeployment"/> for the instance ONLY IF no pending row already
|
|
/// exists for that <c>InstanceId</c>. An existing pending row signals an in-flight deploy that
|
|
/// is already delivering to the node — do NOT supersede it (contrast with
|
|
/// <see cref="AddPendingDeploymentAsync"/>, which supersedes). Returns <c>true</c> if the row
|
|
/// was staged, <c>false</c> if an existing row was found and left untouched.
|
|
/// This method is self-contained: it commits its own save and returns a meaningful result,
|
|
/// matching the convention of <see cref="PurgeExpiredPendingDeploymentsAsync"/>.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance to stage the pending deployment for.</param>
|
|
/// <param name="deploymentId">The deployment ID (fetch key).</param>
|
|
/// <param name="revisionHash">Revision hash of the flattened configuration.</param>
|
|
/// <param name="configurationJson">JSON-serialized flattened configuration.</param>
|
|
/// <param name="token">Short-TTL fetch token the site node will present to the HTTP endpoint.</param>
|
|
/// <param name="createdAtUtc">UTC timestamp for this pending row.</param>
|
|
/// <param name="expiresAtUtc">UTC expiry after which the token is no longer valid.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns><c>true</c> if staged; <c>false</c> if a pending row already existed and was left unchanged.</returns>
|
|
Task<bool> StagePendingIfAbsentAsync(int instanceId, string deploymentId, string revisionHash,
|
|
string configurationJson, string token, DateTimeOffset createdAtUtc, DateTimeOffset expiresAtUtc,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
// Instance lookups for deployment pipeline
|
|
/// <summary>
|
|
/// Gets an instance by ID.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The instance, or null if not found.</returns>
|
|
Task<Instance?> GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Gets an instance by unique name.
|
|
/// </summary>
|
|
/// <param name="uniqueName">The unique instance name.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>The instance, or null if not found.</returns>
|
|
Task<Instance?> GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default);
|
|
/// <summary>
|
|
/// Updates an instance.
|
|
/// </summary>
|
|
/// <param name="instance">The instance to update.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Removes an instance and everything that depends on it: deployment
|
|
/// records, deployed config snapshot, attribute/alarm overrides, and
|
|
/// connection bindings.
|
|
/// </summary>
|
|
/// <param name="instanceId">The instance ID to delete.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the asynchronous operation.</returns>
|
|
Task DeleteInstanceAsync(int instanceId, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Saves all pending changes to the database.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task representing the number of entities saved.</returns>
|
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Abandons every pending (added / modified / deleted) change on the unit of
|
|
/// work, leaving the underlying store untouched, and returns how many entries
|
|
/// were dropped.
|
|
///
|
|
/// <para>
|
|
/// <b>Recovery only.</b> A <see cref="SaveChangesAsync"/> failure does NOT
|
|
/// untrack the entity that caused it: it stays in the change set and is
|
|
/// re-attempted by the NEXT save, so one unsavable entity poisons every
|
|
/// later save made through the same unit of work. That matters most on the
|
|
/// bulk site-deploy path, where all three phases share ONE scoped context
|
|
/// across every instance in the batch — a persistent failure on instance 1
|
|
/// would otherwise fail instance 2..N's status writes as well, including the
|
|
/// cleanup write that exists precisely to stop records being stranded
|
|
/// <c>InProgress</c>. Call this only after a save has already failed and its
|
|
/// pending work has been given up on; anything not yet committed is lost by
|
|
/// design.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <returns>The number of tracked entries detached (0 when nothing was pending).</returns>
|
|
int DetachPendingChanges();
|
|
}
|