Class Table<T>
- Namespace
- DynaSharp
- Assembly
- DynaSharp.dll
A DynamoDB table, and the entry to everything that reads and writes it: Table<Order> reads and writes the entity Order,
Table<Item> the same table without a schema.
public sealed class Table<T> where T : IDynamoDbDocument<T>
Type Parameters
THow items are written and read.
- Inheritance
-
Table<T>
- Inherited Members
- Extension Methods
Examples
var orders = client.Table<Order>("Shop");
await orders.Put(order).IfNotExists().SendAsync(ct);
Order? found = await orders.Get(Order.Keys.Of("alice", 1)).ItemAsync(ct);
await foreach (var open in orders.Query(Order.Keys.Pk.Of("alice")).Filter(Order.Attrs.Status.Eq(OrderStatus.Placed)).ItemsAsync(ct))
{
Console.WriteLine(open.Number);
}
Remarks
Every method starts a command, which is sent with SendAsync or one of its siblings such as ItemAsync: await orders.Get(Order.Keys.Of("alice", 1)).Consistent().SendAsync(ct);.
Each verb is the DynamoDB operation of that name, with its semantics: Put(T) replaces a whole item, Update creates an item
that does not exist unless told otherwise, and nothing is read before it is written.
A table of an entity takes the keys its generator built, Order.Keys.Of(...) and Order.Keys.Pk.Of(...), which format the values the way the item stores them. The values of a
key as they are stored, such as shop.Get("USER#1", "PROFILE"), go to a table without a schema, which client.Table(name, partitionKey, sortKey) opens, see ItemTableExtensions.
Hold it in a field. A table holds what never changes; it is immutable and thread-safe. It makes no request of its own, so opening a table that does not exist succeeds, and the first command fails. The table is sealed and has no interface: the test seam is the HttpMessageHandler of the client, which tests the serialization too.
Properties
Client
Gets the client the table was opened from, which sends every command of the table. Its Options say what the commands run with: the clock, the logger factory, the retries. A table seen through Of<TOther>() and an Index<T> of it have the same client.
public DynamoDbClient Client { get; }
Property Value
Remarks
Code that takes a table and needs the client too, such as DynaSharp.Coordination, which sends transactions and reads the clock of the
client, takes it from here instead of asking for both.
Name
Gets the name of the table, or its ARN.
public string Name { get; }
Property Value
PartitionKeyName
Gets the attribute name of the partition key.
public string PartitionKeyName { get; }
Property Value
SortKeyName
Gets the attribute name of the sort key, or null if the table has none.
public string? SortKeyName { get; }
Property Value
Methods
BatchGet()
Starts a batch get of many items of this table by their keys (BatchGetItem), of any number: the command cuts them into requests of 100 and
asks again for what DynamoDB left unprocessed. Items come back in no order, and it is not a snapshot.
public BatchGetCommand<T> BatchGet()
Returns
- BatchGetCommand<T>
The command.
BatchWrite()
Starts a batch of puts and deletes (BatchWriteItem), of any number and on any tables: the command cuts them into requests that DynamoDB takes and
sends again what it left unprocessed. A batch is not atomic, and has no conditions.
public BatchWriteCommand<T> BatchWrite()
Returns
- BatchWriteCommand<T>
The command.
CreateTable()
Starts the creation of this table from what its entity declares: await orders.CreateTable().Including<Customer>().SendAsync(ct);. One entity does not describe a single table, whose indexes are
the union over its entities, so Including<TOther>() names the others. A table without an entity starts from the key names it was opened with. See CreateTableCommand.
public CreateTableCommand CreateTable()
Returns
- CreateTableCommand
The command, which sends nothing until SendAsync(CancellationToken).
DeleteTableAsync(CancellationToken)
Deletes this table and all the items in it (DeleteTable), which cannot be undone except from a backup. To delete one item, see Delete<TKey>(TKey).
public ValueTask<TableDescription> DeleteTableAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenCancels the call. A deletion DynamoDB accepted stays accepted.
Returns
- ValueTask<TableDescription>
The description of the table as it goes away. A response without one gives the name of this table and Unknown.
Remarks
The table is gone only some time later, and this call does not wait for that: it returns the description of a table that is normally Deleting, and while it is, creating a table of that name fails with ResourceInUseException. It is gone when DescribeTableAsync(CancellationToken) throws ResourceNotFoundException.
A table with deletion protection cannot be deleted. A repeat after a lost response can fail with ResourceNotFoundException or ResourceInUseException, because the table is already going away. The stream of the table stays readable for 24 hours.
Exceptions
- ObjectDisposedException
The client is disposed.
- ResourceNotFoundException
The table does not exist.
- ResourceInUseException
The table is being created or updated, or is already being deleted.
- LimitExceededException
The account creates or deletes tables too fast.
- ValidationException
DynamoDB rejects the request, for example because the table has deletion protection.
- DynamoDbException
DynamoDB returns any other error; a throttling or server error only after the last attempt.
Delete<TKey>(TKey)
Starts a delete of one item by a generated key. Deleting what does not exist is no error unless IfExists() asks for one.
public DeleteCommand<T> Delete<TKey>(TKey key) where TKey : IKey<T>
Parameters
keyTKeyThe key.
Returns
- DeleteCommand<T>
The command.
Type Parameters
TKeyThe key. A key of another entity is a compile error.
DescribeTableAsync(CancellationToken)
Reads the description of the table: status, key schema, indexes and approximate counts, which DynamoDB updates about every six hours.
public ValueTask<TableDescription> DescribeTableAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenCancels the call.
Returns
- ValueTask<TableDescription>
The description. A response without one gives the name of this table and Unknown.
Exceptions
- ObjectDisposedException
The client is disposed.
- ResourceNotFoundException
The table does not exist, or it was created a moment ago: this call is eventually consistent.
- DynamoDbException
DynamoDB returns any other error; a throttling or server error only after the last attempt.
Get<TKey>(TKey)
Starts a GetItem by a key the generator built, such as customers.Get(Customer.Keys.Of("alice")). End the chain with ItemAsync(CancellationToken) or
SendAsync(CancellationToken); a missing item is not an error.
public GetCommand<T> Get<TKey>(TKey key) where TKey : IKey<T>
Parameters
keyTKeyThe key.
Returns
- GetCommand<T>
The command.
Type Parameters
TKeyThe key. A key of another entity is a compile error.
Index(string, string, string?)
Gets a secondary index, naming its keys. An index is read through its own type, which has no writes. Hold it in a field.
public Index<T> Index(string name, string partitionKey, string? sortKey = null)
Parameters
namestringThe name of the index.
partitionKeystringThe attribute name of the partition key of the index.
sortKeystringThe attribute name of the sort key of the index, or null if it has none.
Returns
- Index<T>
The index.
Exceptions
- ArgumentException
nameorpartitionKeyis null or empty.
Of<TOther>()
Views this table as another entity type, for a single table that holds several types: shop.Of<Order>().Get(key).
public Table<TOther> Of<TOther>() where TOther : IDynamoDbEntity<TOther>
Returns
- Table<TOther>
The table.
Type Parameters
TOtherThe entity. A nested document has no key, so it does not fit here, which the constraint enforces.
Remarks
Key names, indexes and the discriminator come from TOther. The view is created once per type and shared, so it is fine to call this inline.
Put(T)
Starts a put of a whole item. The item is written to the request here and now, so changing it afterwards changes nothing. A put replaces
everything under the key; IfNotExists() makes it a create. An entity with a VersionAttribute adds the condition on its version and stores
the next one, see there, unless IgnoreVersion<T>(PutCommand<T>) says otherwise. It takes the version from the entity, so a put has no IfVersion: put the entity as it was read.
public PutCommand<T> Put(T item)
Parameters
itemTThe item, which must hold the key attributes.
Returns
- PutCommand<T>
The command.
Exceptions
- ArgumentNullException
itemis null.
Query<TKey>(TKey)
Starts a Query for the one item of a key, which is a Get that can take a filter: orders.Query(Order.Keys.Of(id, day)).Filter(open).ItemAsync(ct).
public QueryCommand<T> Query<TKey>(TKey key) where TKey : IKey<T>
Parameters
keyTKeyThe key.
Returns
- QueryCommand<T>
The command.
Type Parameters
TKeyThe key. A key of another entity is a compile error.
Remarks
It queries the partition key, with Eq on the sort key if the key has one, so it finds at most one item. A read of an entity filters on its discriminator, so an item of another
type under the key is no match, where a Get would fail on it. A partition key such as Order.Keys.Pk.Of(id) is no key of an item, and goes to the overloads that take a condition on the sort key.
Exceptions
- ArgumentNullException
keyis null.
ReadChangesAsync<T1, T2>(DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, Action<DynamoDbMappingException, ShardChangeRecord>?, CancellationToken)
Reads the changes of 2 entities of a table from its stream, typed: shop.ReadChangesAsync<Customer, Order>(streams, ShardIteratorType.Latest, cancellationToken: ct)
gives a MixedChange<T1, T2> per change of one of them, which Is(out ...) and Match tell apart, and skips the other entities of the table.
public IAsyncEnumerable<MixedChange<T1, T2>> ReadChangesAsync<T1, T2>(DynamoDbStreamsClient streams, ShardIteratorType shardIteratorType, TimeSpan? emptyPageDelay = null, Action? onJoined = null, Action<DynamoDbMappingException, ShardChangeRecord>? onMappingFailure = null, CancellationToken cancellationToken = default) where T1 : IDynamoDbDiscriminated<T1> where T2 : IDynamoDbDiscriminated<T2>
Parameters
streamsDynamoDbStreamsClientThe client the stream is read with, for the same account and region as the client of the table.
shardIteratorTypeShardIteratorTypeWhere reading starts, see ShardIteratorType.
emptyPageDelayTimeSpan?The wait after an empty page, see EmptyPageDelay, or null for its default of one second.
onJoinedActionWhat is called once the reading holds a position in every shard it starts with, see OnJoined, or null.
onMappingFailureAction<DynamoDbMappingException, ShardChangeRecord>Called when an image has the discriminator of a type of the read but cannot be read as it; the change is then skipped, and counted in
dynasharp.client.operation.skipped_items. null, the default, ends the read with the DynamoDbMappingException instead. Log ShardId, SequenceNumber and EventName, not the record: it holds the values of the item.cancellationTokenCancellationTokenCancels the reading. A token given to
WithCancellationworks too.
Returns
- IAsyncEnumerable<MixedChange<T1, T2>>
The changes of the entities, in the order ReadStreamAsync(ReadStreamRequest, CancellationToken) returns their records.
Type Parameters
T1The first entity.
T2The second entity.
Remarks
The view must be NewAndOldImages. The read is that of ReadStreamAsync<T>(Table<T>, DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, CancellationToken), with its DescribeTable at the start of each
enumeration, and it also checks the view there: without the old image a removal could not be typed and would be missed without a word, and without the new image an insert would.
Another view throws InvalidOperationException; As<T>() types what a record of any view has.
Every change is read. A stream cannot be filtered at its source, so every change of the table is read and typed here, from the discriminator of its images, and a change of no type of the read is skipped. An item whose type changed at its key gives two changes, the removal of the old type, then the insert of the new one, each when its type is read. Arguments are checked at the call, and nothing is requested until the first change is asked for.
Exceptions
- ArgumentNullException
streamsis null.- ArgumentException
The types cannot be told apart: a discriminator occurs twice, or they keep it in different attributes, as for
Of<...>()of a query.- ArgumentOutOfRangeException
shardIteratorTypeis neither TrimHorizon nor Latest, oremptyPageDelayis negative or 15 minutes or more.- InvalidOperationException
The table has no enabled stream, or its view is not NewAndOldImages (raised while reading).
- DynamoDbMappingException
An image of a type cannot be read as it and
onMappingFailureis null (raised while reading).- ResourceNotFoundException
The table does not exist (raised while reading), or what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws it for.
- DynamoDbException
DescribeTablefails, or the reading of the stream does, with what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws, TrimmedDataAccessException among it.- OperationCanceledException
cancellationTokenwas cancelled.
ReadChangesAsync<T1, T2, T3>(DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, Action<DynamoDbMappingException, ShardChangeRecord>?, CancellationToken)
Reads the changes of 3 entities of a table from its stream, typed: shop.ReadChangesAsync<Customer, Order, Invoice>(streams, ShardIteratorType.Latest, cancellationToken: ct)
gives a MixedChange<T1, T2, T3> per change of one of them, which Is(out ...) and Match tell apart, and skips the other entities of the table.
public IAsyncEnumerable<MixedChange<T1, T2, T3>> ReadChangesAsync<T1, T2, T3>(DynamoDbStreamsClient streams, ShardIteratorType shardIteratorType, TimeSpan? emptyPageDelay = null, Action? onJoined = null, Action<DynamoDbMappingException, ShardChangeRecord>? onMappingFailure = null, CancellationToken cancellationToken = default) where T1 : IDynamoDbDiscriminated<T1> where T2 : IDynamoDbDiscriminated<T2> where T3 : IDynamoDbDiscriminated<T3>
Parameters
streamsDynamoDbStreamsClientThe client the stream is read with, for the same account and region as the client of the table.
shardIteratorTypeShardIteratorTypeWhere reading starts, see ShardIteratorType.
emptyPageDelayTimeSpan?The wait after an empty page, see EmptyPageDelay, or null for its default of one second.
onJoinedActionWhat is called once the reading holds a position in every shard it starts with, see OnJoined, or null.
onMappingFailureAction<DynamoDbMappingException, ShardChangeRecord>Called when an image has the discriminator of a type of the read but cannot be read as it; the change is then skipped, and counted in
dynasharp.client.operation.skipped_items. null, the default, ends the read with the DynamoDbMappingException instead. Log ShardId, SequenceNumber and EventName, not the record: it holds the values of the item.cancellationTokenCancellationTokenCancels the reading. A token given to
WithCancellationworks too.
Returns
- IAsyncEnumerable<MixedChange<T1, T2, T3>>
The changes of the entities, in the order ReadStreamAsync(ReadStreamRequest, CancellationToken) returns their records.
Type Parameters
T1The first entity.
T2The second entity.
T3The third entity.
Remarks
The view must be NewAndOldImages. The read is that of ReadStreamAsync<T>(Table<T>, DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, CancellationToken), with its DescribeTable at the start of each
enumeration, and it also checks the view there: without the old image a removal could not be typed and would be missed without a word, and without the new image an insert would.
Another view throws InvalidOperationException; As<T>() types what a record of any view has.
Every change is read. A stream cannot be filtered at its source, so every change of the table is read and typed here, from the discriminator of its images, and a change of no type of the read is skipped. An item whose type changed at its key gives two changes, the removal of the old type, then the insert of the new one, each when its type is read. Arguments are checked at the call, and nothing is requested until the first change is asked for.
Exceptions
- ArgumentNullException
streamsis null.- ArgumentException
The types cannot be told apart: a discriminator occurs twice, or they keep it in different attributes, as for
Of<...>()of a query.- ArgumentOutOfRangeException
shardIteratorTypeis neither TrimHorizon nor Latest, oremptyPageDelayis negative or 15 minutes or more.- InvalidOperationException
The table has no enabled stream, or its view is not NewAndOldImages (raised while reading).
- DynamoDbMappingException
An image of a type cannot be read as it and
onMappingFailureis null (raised while reading).- ResourceNotFoundException
The table does not exist (raised while reading), or what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws it for.
- DynamoDbException
DescribeTablefails, or the reading of the stream does, with what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws, TrimmedDataAccessException among it.- OperationCanceledException
cancellationTokenwas cancelled.
ReadChangesAsync<T1, T2, T3, T4>(DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, Action<DynamoDbMappingException, ShardChangeRecord>?, CancellationToken)
Reads the changes of 4 entities of a table from its stream, typed: shop.ReadChangesAsync<Customer, Order, Invoice, Payment>(streams, ShardIteratorType.Latest, cancellationToken: ct)
gives a MixedChange<T1, T2, T3, T4> per change of one of them, which Is(out ...) and Match tell apart, and skips the other entities of the table.
public IAsyncEnumerable<MixedChange<T1, T2, T3, T4>> ReadChangesAsync<T1, T2, T3, T4>(DynamoDbStreamsClient streams, ShardIteratorType shardIteratorType, TimeSpan? emptyPageDelay = null, Action? onJoined = null, Action<DynamoDbMappingException, ShardChangeRecord>? onMappingFailure = null, CancellationToken cancellationToken = default) where T1 : IDynamoDbDiscriminated<T1> where T2 : IDynamoDbDiscriminated<T2> where T3 : IDynamoDbDiscriminated<T3> where T4 : IDynamoDbDiscriminated<T4>
Parameters
streamsDynamoDbStreamsClientThe client the stream is read with, for the same account and region as the client of the table.
shardIteratorTypeShardIteratorTypeWhere reading starts, see ShardIteratorType.
emptyPageDelayTimeSpan?The wait after an empty page, see EmptyPageDelay, or null for its default of one second.
onJoinedActionWhat is called once the reading holds a position in every shard it starts with, see OnJoined, or null.
onMappingFailureAction<DynamoDbMappingException, ShardChangeRecord>Called when an image has the discriminator of a type of the read but cannot be read as it; the change is then skipped, and counted in
dynasharp.client.operation.skipped_items. null, the default, ends the read with the DynamoDbMappingException instead. Log ShardId, SequenceNumber and EventName, not the record: it holds the values of the item.cancellationTokenCancellationTokenCancels the reading. A token given to
WithCancellationworks too.
Returns
- IAsyncEnumerable<MixedChange<T1, T2, T3, T4>>
The changes of the entities, in the order ReadStreamAsync(ReadStreamRequest, CancellationToken) returns their records.
Type Parameters
T1The first entity.
T2The second entity.
T3The third entity.
T4The fourth entity.
Remarks
The view must be NewAndOldImages. The read is that of ReadStreamAsync<T>(Table<T>, DynamoDbStreamsClient, ShardIteratorType, TimeSpan?, Action?, CancellationToken), with its DescribeTable at the start of each
enumeration, and it also checks the view there: without the old image a removal could not be typed and would be missed without a word, and without the new image an insert would.
Another view throws InvalidOperationException; As<T>() types what a record of any view has.
Every change is read. A stream cannot be filtered at its source, so every change of the table is read and typed here, from the discriminator of its images, and a change of no type of the read is skipped. An item whose type changed at its key gives two changes, the removal of the old type, then the insert of the new one, each when its type is read. Arguments are checked at the call, and nothing is requested until the first change is asked for.
Exceptions
- ArgumentNullException
streamsis null.- ArgumentException
The types cannot be told apart: a discriminator occurs twice, or they keep it in different attributes, as for
Of<...>()of a query.- ArgumentOutOfRangeException
shardIteratorTypeis neither TrimHorizon nor Latest, oremptyPageDelayis negative or 15 minutes or more.- InvalidOperationException
The table has no enabled stream, or its view is not NewAndOldImages (raised while reading).
- DynamoDbMappingException
An image of a type cannot be read as it and
onMappingFailureis null (raised while reading).- ResourceNotFoundException
The table does not exist (raised while reading), or what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws it for.
- DynamoDbException
DescribeTablefails, or the reading of the stream does, with what ReadStreamAsync(ReadStreamRequest, CancellationToken) throws, TrimmedDataAccessException among it.- OperationCanceledException
cancellationTokenwas cancelled.
RetryOnConflictAsync(Func<int, CancellationToken, ValueTask>, ConflictKind, int, CancellationToken)
Runs a write that returns nothing, with the number of the run, up to maxAttempts times, and again only on the conflicts that retryOn names. See
RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken).
public Task RetryOnConflictAsync(Func<int, CancellationToken, ValueTask> operation, ConflictKind retryOn = ConflictKind.Any, int maxAttempts = 5, CancellationToken cancellationToken = default)
Parameters
operationFunc<int, CancellationToken, ValueTask>One run. It gets the number of the run and
cancellationToken.retryOnConflictKindThe conflicts that run it again: Any, the default, ConditionalCheckFailed or TransactionConflict.
maxAttemptsintHow many runs there can be, the first one included, so 1 means no second run. The default is 5.
cancellationTokenCancellationTokenPassed to every run; it also ends the wait between two runs. Because
maxAttemptscomes before it, pass it by name:cancellationToken: ct.
Returns
- Task
A task that completes when a run got through.
Exceptions
- ArgumentNullException
operationis null.- ArgumentOutOfRangeException
retryOnis not one of the three values, ormaxAttemptsis below 1.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of a run failed, of the last run when
retryOnnames it, else of the first that failed.- TransactionConflictException
A run met an ongoing transaction, the last run when
retryOnnames it, else the first that met one.- TransactionCanceledException
The last run was cancelled with a named conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync(Func<int, CancellationToken, ValueTask>, CancellationToken)
Runs a read-modify-write that returns nothing, with the number of the run. See RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>>, CancellationToken).
public Task RetryOnConflictAsync(Func<int, CancellationToken, ValueTask> operation, CancellationToken cancellationToken)
Parameters
operationFunc<int, CancellationToken, ValueTask>One run. It gets the number of the run and
cancellationToken.cancellationTokenCancellationTokenGoes to each run, and ends a wait between two runs at once.
Returns
- Task
A task that completes when a run got through.
Exceptions
- ArgumentNullException
operationis null.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of the last run failed. Key says on which item.
- TransactionConflictException
The last run met an ongoing transaction.
- TransactionCanceledException
The last run was cancelled with a conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync(Func<CancellationToken, ValueTask>, ConflictKind, int, CancellationToken)
Runs a write that returns nothing up to maxAttempts times, and again only on the conflicts that retryOn names. See
RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken).
public Task RetryOnConflictAsync(Func<CancellationToken, ValueTask> operation, ConflictKind retryOn = ConflictKind.Any, int maxAttempts = 5, CancellationToken cancellationToken = default)
Parameters
operationFunc<CancellationToken, ValueTask>One run. It gets
cancellationToken.retryOnConflictKindThe conflicts that run it again: Any, the default, ConditionalCheckFailed or TransactionConflict.
maxAttemptsintHow many runs there can be, the first one included, so 1 means no second run. The default is 5.
cancellationTokenCancellationTokenPassed to every run; it also ends the wait between two runs. Because
maxAttemptscomes before it, pass it by name:cancellationToken: ct.
Returns
- Task
A task that completes when a run got through.
Exceptions
- ArgumentNullException
operationis null.- ArgumentOutOfRangeException
retryOnis not one of the three values, ormaxAttemptsis below 1.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of a run failed, of the last run when
retryOnnames it, else of the first that failed.- TransactionConflictException
A run met an ongoing transaction, the last run when
retryOnnames it, else the first that met one.- TransactionCanceledException
The last run was cancelled with a named conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync(Func<CancellationToken, ValueTask>, CancellationToken)
Runs a read-modify-write that returns nothing, and runs it again when it loses to another writer. See RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, CancellationToken) for what a run is, what makes it run again and how to write one.
public Task RetryOnConflictAsync(Func<CancellationToken, ValueTask> operation, CancellationToken cancellationToken)
Parameters
operationFunc<CancellationToken, ValueTask>One run. It gets
cancellationToken.cancellationTokenCancellationTokenGoes to each run, and ends a wait between two runs at once.
Returns
- Task
A task that completes when a run got through.
Exceptions
- ArgumentNullException
operationis null.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of the last run failed. Key says on which item.
- TransactionConflictException
The last run met an ongoing transaction.
- TransactionCanceledException
The last run was cancelled with a conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken)
Runs a write with the number of the run up to maxAttempts times, and again only on the conflicts that retryOn names. See
RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>>, CancellationToken) for the number, and RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken) for the rest.
public Task<TResult> RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>> operation, ConflictKind retryOn = ConflictKind.Any, int maxAttempts = 5, CancellationToken cancellationToken = default)
Parameters
operationFunc<int, CancellationToken, ValueTask<TResult>>One run. It gets the number of the run and
cancellationToken.retryOnConflictKindThe conflicts that run it again: Any, the default, ConditionalCheckFailed or TransactionConflict.
maxAttemptsintHow many runs there can be, the first one included, so 1 means no second run. The default is 5.
cancellationTokenCancellationTokenPassed to every run; it also ends the wait between two runs. Because
maxAttemptscomes before it, pass it by name:cancellationToken: ct.
Returns
- Task<TResult>
What the run that got through returned.
Type Parameters
TResultWhat a run returns.
Exceptions
- ArgumentNullException
operationis null.- ArgumentOutOfRangeException
retryOnis not one of the three values, ormaxAttemptsis below 1.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of a run failed, of the last run when
retryOnnames it, else of the first that failed.- TransactionConflictException
A run met an ongoing transaction, the last run when
retryOnnames it, else the first that met one.- TransactionCanceledException
The last run was cancelled with a named conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>>, CancellationToken)
Runs a read-modify-write that gets the number of its run, 1 for the first, and repeats it when it loses to another writer, as RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, CancellationToken) does.
public Task<TResult> RetryOnConflictAsync<TResult>(Func<int, CancellationToken, ValueTask<TResult>> operation, CancellationToken cancellationToken)
Parameters
operationFunc<int, CancellationToken, ValueTask<TResult>>One run. It gets the number of the run and
cancellationToken.cancellationTokenCancellationTokenGoes to each run, and ends a wait between two runs at once.
Returns
- Task<TResult>
What the run that got through returned.
Type Parameters
TResultWhat a run returns.
Examples
A value read before the call, which the first run takes and every later run reads again:
var cart = await carts.Get(Cart.Keys.Of("bob")).ItemAsync(ct);
await carts.RetryOnConflictAsync(async (run, token) =>
{
var current = run == 1 ? cart : await carts.Get(Cart.Keys.Of("bob")).Consistent().ItemAsync(token);
await carts.Put(current! with { Lines = lines }).SendAsync(token);
}, ct);
Remarks
Only the first run may take what was read before: the run after a conflict has lost to a write that this value does not hold, and taking it again fails the same way, which is what DYNA218 reports for a version from before the call.
Exceptions
- ArgumentNullException
operationis null.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of the last run failed. Key says on which item.
- TransactionConflictException
The last run met an ongoing transaction.
- TransactionCanceledException
The last run was cancelled with a conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken)
Runs a write up to maxAttempts times, and again only on the conflicts that retryOn names:
RetryOnConflictAsync(operation, ConflictKind.TransactionConflict, cancellationToken: ct). See
RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, CancellationToken) for what a run is and how to write one.
public Task<TResult> RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>> operation, ConflictKind retryOn = ConflictKind.Any, int maxAttempts = 5, CancellationToken cancellationToken = default)
Parameters
operationFunc<CancellationToken, ValueTask<TResult>>One run. It gets
cancellationToken.retryOnConflictKindThe conflicts that run it again: Any, the default, ConditionalCheckFailed or TransactionConflict.
maxAttemptsintHow many runs there can be, the first one included, so 1 means no second run. The default is 5.
cancellationTokenCancellationTokenPassed to every run; it also ends the wait between two runs. Because
maxAttemptscomes before it, pass it by name:cancellationToken: ct.
Returns
- Task<TResult>
What the run that got through returned.
Type Parameters
TResultWhat a run returns.
Examples
A registration whose claims are a transaction: a taken email is the answer, and only a transaction in the way is asked again.
var registered = await customers.RetryOnConflictAsync(async token =>
{
TransactMarker taken = default;
try
{
await client.TransactWrite().Unique(emails, new EmailClaim(customer.Email, customer.Id), out taken).Put(customers, customer).IfNotExists().SendAsync(token);
return true;
}
catch (TransactionCanceledException exception) when (exception.ReasonFor(taken) == TransactionFailure.ConditionalCheckFailed)
{
return false;
}
}, ConflictKind.TransactionConflict, cancellationToken: ct);
Remarks
TransactionConflict is for a run whose failed condition is an answer that asking again does not change, such as a value that is taken, or a record that exists. It surfaces at once, with no wait and no second run, and so does anything else that is not named. A cancelled transaction runs again when any of its reasons is of a kind named here, whatever the others say, and the limit on the runs ends it when another reason does not pass.
ConditionalCheckFailed alone leaves a transaction in the way to the caller, which a run that never meets a transaction does not need either way.
Every failed condition on a version means that another writer got through, so n writers on one item need at most n runs each, whatever the timing. That is the number to pass
as maxAttempts when more than five writers meet on one item. TransactionConflict has no such bound, because the transaction that was in the way may fail too.
Exceptions
- ArgumentNullException
operationis null.- ArgumentOutOfRangeException
retryOnis not one of the three values, ormaxAttemptsis below 1.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of a run failed, of the last run when
retryOnnames it, else of the first that failed.- TransactionConflictException
A run met an ongoing transaction, the last run when
retryOnnames it, else the first that met one.- TransactionCanceledException
The last run was cancelled with a named conflict among its reasons, or any run was cancelled with none.
RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, CancellationToken)
Runs a read-modify-write and repeats it, up to 5 runs with a short wait between them, when it loses to another writer: a ConditionalCheckFailedException, a TransactionConflictException, or a TransactionCanceledException with one of those reasons. The failure of the last run is rethrown.
public Task<TResult> RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>> operation, CancellationToken cancellationToken)
Parameters
operationFunc<CancellationToken, ValueTask<TResult>>One run. It gets
cancellationToken, and it must read what it depends on itself, see the remarks.cancellationTokenCancellationTokenGoes to each run, and ends a wait between two runs at once.
Returns
- Task<TResult>
What the run that got through returned.
Type Parameters
TResultWhat a run returns.
Examples
var saved = await carts.RetryOnConflictAsync(async token =>
{
var cart = await carts.Get(Cart.Keys.Of("bob")).Consistent().ItemAsync(token);
return await carts.Update(Cart.Keys.Of("bob")).Set(Cart.Attrs.Lines, lines).IfVersion(cart!.Revision).SendAsync(token);
}, ct);
Remarks
No command retries a conflict by itself. This is the loop to ask for it. Nothing ties a run to this table: it can read and write others, and send a transaction. The overload that takes a ConflictKind repeats one kind of conflict only, for a run whose failed condition is its answer, and takes another number of runs: RetryOnConflictAsync<TResult>(Func<CancellationToken, ValueTask<TResult>>, ConflictKind, int, CancellationToken).
Read inside the delegate. Each run starts from nothing, so operation reads, decides and writes. A write built from what was read before this call
carries the same old version in every run, so every run fails the same way and the retry changes nothing. Make it a strongly consistent read, because another read can miss the
write this run lost to.
A condition can fail for a business reason too. A failed condition does not say why, and it can just as well mean that the bucket is empty or the seat is taken. The delegate reads again, so it decides again: it sees what is there now and returns without writing. A rule the delegate cannot read, such as a check on an item that it does not load, it catches itself and turns into its result, because whatever leaves the delegate as one of the three exceptions counts as lost. A transaction runs again if any of its reasons is one of the two, whatever the other actions report, and the limit on the runs is what ends it when another reason does not pass. A run that reads nothing cannot decide again, so a failed condition there is the answer, and TransactionConflict lets it surface at once.
Everything else surfaces at once: any other exception of a run, also from the third run after two conflicts, and cancellation. After the last run its exception surfaces unchanged, so a ConditionalCheckFailedException of the last run says which item gave up in Key, which holds the key of a write of that run as the request named it. Neither its message nor telemetry show the key, and a transaction has none: there ItemFor(TransactMarker) is the way to an item.
Keep follow-up writes outside the run. A run is one read-modify-write, and what depends on its result, such as a second item or an entry of an audit log, goes after this call. Inside, it runs again with every run. A retry inside a run multiplies the attempts, 5 runs of 5 are 25, and an inner one that used up its runs throws what it last caught, which this call counts as a conflict of its own, so one lost race on the inner item starts the whole run over.
A wait. Between two runs it waits a random time up to min(1 s, 10 ms * 2^(run - 1)) on TimeProvider, so that writers that failed together
do not meet again; a conflict usually clears within that. This has nothing to do with MaxAttempts: it takes nothing from the retry quota, and every
call of a run has its own attempts, as it would outside.
A lost response can look like a conflict. When a conditional write reaches DynamoDB and its response is lost, the next attempt of that call fails its condition on what the first
attempt wrote, and the delegate then runs on top of its own write. A run that adds to what it read, a deposit or a line in a cart, adds twice. Give such a write
MaxAttempts(1): the lost response then surfaces as a failure of the call, and it is not retried here. A transaction does not have this problem, because the attempts of one send carry
one ClientRequestToken.
In a transaction, leave Token(string) out. A run that read something else sends another request, and a token says that it is the same one, which DynamoDB answers with
IdempotentParameterMismatchException.
Telemetry. The calls of a run have their spans and metrics as usual. On top of that dynasharp.client.conflict.retries counts each run that lost and is repeated, and
dynasharp.client.conflict.exhausted each conflict passed on after the last run, both by the operation, the table and the error that the conflict names. A warning is
logged when the runs are used up. Neither carries a key or a value.
Exceptions
- ArgumentNullException
operationis null.- OperationCanceledException
cancellationTokenwas cancelled during a wait, with that token. A run reports it in its own way.- ConditionalCheckFailedException
The condition of the last run failed. Key says on which item, see the remarks.
- TransactionConflictException
The last run met an ongoing transaction.
- TransactionCanceledException
The last run was cancelled with a conflict among its reasons, or any run was cancelled with none.
Scan()
Starts a scan of the whole table, which charges for every item in it, see ScanCommand<T>. A scan of a table of an entity gives that entity's items.
public ScanCommand<T> Scan()
Returns
- ScanCommand<T>
The command.
Update<TKey>(TKey)
Starts an UpdateItem by a key the generator built, such as orders.Update(Order.Keys.Of(customerId, number)). It creates the item if none has the key, unless
IfExists() says otherwise, so an update of an entity with a discriminator gives a new item its discriminator, see UpdateCommand<T>.
public UpdateCommand<T> Update<TKey>(TKey key) where TKey : IKey<T>
Parameters
keyTKeyThe key.
Returns
- UpdateCommand<T>
The command.
Type Parameters
TKeyThe key. A key of another entity is a compile error.
WaitUntilActiveAsync(CancellationToken)
Waits for at most five minutes until the table and its global secondary indexes are ACTIVE. See WaitUntilActiveAsync(TimeSpan, CancellationToken), which an index
that UpdateTable added needs, because that takes longer.
public ValueTask<TableDescription> WaitUntilActiveAsync(CancellationToken cancellationToken = default)
Parameters
cancellationTokenCancellationTokenCancels the wait.
Returns
- ValueTask<TableDescription>
The description of the active table.
Exceptions
- TableNotActiveException
The table or an index of it was not active in time.
- TimeoutException
A poll ran out of AttemptTimeout on its last attempt.
- OperationCanceledException
cancellationTokenwas cancelled.- ObjectDisposedException
The client is disposed.
- DynamoDbException
DynamoDB returns an error other than ResourceNotFoundException; a throttling or server error only after the last attempt.
WaitUntilActiveAsync(TimeSpan, CancellationToken)
Waits until the table and all its global secondary indexes, those its entity declares among them, are ACTIVE, polling DescribeTable at growing intervals from 100 ms to 5 s.
public ValueTask<TableDescription> WaitUntilActiveAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
Parameters
timeoutTimeSpanHow long to wait, which for an index that
UpdateTableadded is half an hour or more, longer for a large table. InfiniteTimeSpan is not a bound and is rejected.cancellationTokenCancellationTokenCancels the wait.
Returns
- ValueTask<TableDescription>
The description of the active table.
Remarks
On the clock of the client. The polls wait on TimeProvider, so a test with a fake clock does not wait. The wait is bounded: the last poll starts at the latest when timeout is up, and then the call throws TableNotActiveException, which is a TimeoutException
with the name of the table and its last status, whatever that status is. A poll
itself takes as long as a call takes, up to AttemptTimeout per attempt, so the call can return that much later.
With a timeout of zero it looks once. A cancellationToken on the same timeout races it, and when the token wins the caller gets a bare TaskCanceledException or OperationCanceledException, with no table and no
index named, so give the wait less time than the token.
A missing table counts as not active yet, because DescribeTable right after CreateTable can miss a new table. So a wrong name shows
up as a timeout with no LastStatus, whose InnerException is the ResourceNotFoundException. A status that never turns active, such as
DELETING, runs into the timeout too.
An index that was added later. A table reads ACTIVE while an index it was given by UpdateTable backfills, and the index misses items until it is done, so this waits for the indexes too, and
PendingIndex names the one that held it up. DynamoDB first provisions such an index, which takes minutes on any table, often more than five, and then backfills it, which takes as long as the table is large and can take hours.
So that wait needs a longer timeout than the default of five minutes: half an hour for a small table, more for a large one.
Local secondary indexes have no status: they exist from the creation of the table.
An index the entity declares that was just added may not be listed yet, and what is not listed cannot be seen to be behind. So every global index that the entity declares has to be listed as well, and until then this keeps polling; a
table that does not have such an index at all runs into the timeout, and the message of the TableNotActiveException says that it was not listed. A table opened without an entity declares nothing, so
client.Table(name, "pk", "sk") waits for what the table lists, which is the wait before UpdateTable adds an index, and Of<TOther>() gives a table that waits for what that entity declares.
Exceptions
- ArgumentOutOfRangeException
timeoutis negative.- TableNotActiveException
The table or an index of it was not active in time.
- TimeoutException
A poll ran out of AttemptTimeout on its last attempt.
- OperationCanceledException
cancellationTokenwas cancelled.- ObjectDisposedException
The client is disposed.
- DynamoDbException
DynamoDB returns an error other than ResourceNotFoundException; a throttling or server error only after the last attempt.