Class VersionAttribute
Marks the property that holds the version of an item, for optimistic locking: a new item has version 0, each write stores the version plus 1, and a write of a stale copy fails with ConditionalCheckFailedException. It goes on one integer property of an entity that is not nullable, has no custom converter and is part of no key.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class VersionAttribute : Attribute
- Inheritance
-
VersionAttribute
- Inherited Members
Remarks
Every write. The property holds the version the caller last read, and every write of the typed API checks it, with a condition that shows in ToRequestJson(). A Put
and a Delete of an entity take the version from it: attribute_not_exists for 0, version = :old for any other. The put stores the next version without changing the
entity, so the next write takes Version, or the entity read again. A key does not carry a version, so Update and a Delete by key need
.IfVersion(n), or .IgnoreVersion(), and sending without either throws InvalidOperationException. A BatchWriteCommand<T> cannot carry a condition, so it
takes .IgnoreVersion() only. Every write of the entity but a delete advances the version, also without the check, so a copy read before it fails its next checked write, see
IgnoreVersion<T>(UpdateCommand<T>).
A lost race. A write that loses fails with ConditionalCheckFailedException, or in a transaction with a TransactionCanceledException, whose marker of the action says why. Nothing is read or retried by itself: RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, CancellationToken) repeats code that reads the version again.
Without the check. .IgnoreVersion() writes without the condition on the version, for an import or a migration; see
IgnoreVersion<T>(PutCommand<T>) and IgnoreVersion<T>(UpdateCommand<T>).
Version 0. It checks the key and never looks at the version attribute: every 0, on a Put, in .IfVersion(0), on a Delete of an entity and on a step of a transaction,
renders attribute_not_exists on the partition key. So a first write fails on any item under its key, also on one without a version attribute, such as one written by other code: that
item reads as version 0, and a Put at 0 then fails with ConditionalCheckFailedException, because 0 says that no item has the key. .IfNotExists() at version 0 is
the same condition and renders once; above version 0 it throws, because no item can lack its key and hold a version. .IfExists() at version 0 throws for the same reason the other way
around, unless .IgnoreVersion() lifts it as it lifts that rule.
Items without a version. A table that keeps such items, because other code wrote them or they are older than the version, is what [Version(Missing = VersionMissing.Allow)]
is for, and that is the first answer for it. It takes over an item that holds 0 as well, which is what the first SaveAsync of the AWS SDK stores, because that reads as 0 too.
Every 0 above then renders (attribute_not_exists(version) OR version = 0), so 0 means an item without a version, one that holds 0, or none: the first write replaces such an item
and stores version 1, and the writes after it are ordinary ones. A read gives 0 for an item without a version, also for a required version, and so does Select of the handle,
where it would throw otherwise. No item at all is something else: Select has Found false, and Value throws. The
price is that a write at 0 no longer proves that the key was free. Two clauses say what they mean instead: .IfNotExists() at version 0 keeps attribute_not_exists on the
partition key alone, because that is what was asked for, and .IfExists() at version 0 no longer throws, because such an item can exist, and renders attribute_exists on the
partition key beside that same condition on the version attribute. A version above 0 is the same either way.
Taking over a single item. Without changing the attribute, put it once without the check: orders.Put(order).IgnoreVersion() of the entity as it was read, at 0, stores
version 1, and the writes after it are ordinary ones.
Untyped access. The table without a schema and the requests of DynaSharp.Requests know no entity, so they do not enforce a version.
Constructors
VersionAttribute()
public VersionAttribute()
Properties
Missing
Gets or sets what an item without the version attribute means, Reject by default, and Allow for a table that keeps such items. See the remarks of this class.
public VersionMissing Missing { get; set; }