Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions content/develop/clients/dotnet/condexec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
categories:
- docs
- develop
- stack
- oss
- rs
- rc
- oss
- kubernetes
- clients
description: Understand how `NRedisStack` uses conditional execution
linkTitle: Conditional execution
title: Conditional execution
weight: 6
---

Most Redis client libraries use transactions with the
[`WATCH`]({{< relref "/commands/watch" >}}) command as the main way to prevent
two clients writing to the same key at once (see [Transactions]({{< relref "/develop/interact/transactions" >}}) for more information). Unfortunately, this approach is
difficult to use explicitly in `NRedisStack`. Its
[multiplexing]({{< relref "/develop/clients/pools-and-muxing" >}}) system
is highly efficient and convenient but can also cause bad interactions
when different connections use watched transactions at the same time.

Instead, `NRedisStack` relies more heavily on conditional execution. This comes
in two basic forms, `When` conditions and transaction conditions, both of which
are explained in the sections below.

## `When` conditions

Several commands have variants that only execute if the key they change
already exists (or alternatively, if it doesn't already exist). For
example, the [`SET`]({{< relref "/commands/set" >}}) command has the
variants [`SETEX`]({{< relref "/commands/setex" >}}) (set when the key exists),
and [`SETNX`]({{< relref "/commands/setnx" >}}) (set when the key doesn't exist).

Instead of providing the different variants of these commands, `NRedisStack`
lets you add a `When` condition to the basic command to access its variants.
The following example demonstrates this for the
[`HashSet()`]({{< relref "/commands/hset" >}}) command.

<!-- < clients-example pipe_trans_tutorial when_condition "C#" >}}
< /clients-example >}} -->

```csharp
bool resp7 = db.HashSet("Details", "SerialNumber", "12345");
Console.WriteLine(resp7); // >>> true

db.HashSet("Details", "SerialNumber", "12345A", When.NotExists);
string resp8 = db.HashGet("Details", "SerialNumber");
Console.WriteLine(resp8); // >>> 12345

db.HashSet("Details", "SerialNumber", "12345A");
string resp9 = db.HashGet("Details", "SerialNumber");
Console.WriteLine(resp9); // >>> 12345A
```

The available conditions are `When.Exists`, `When.NotExists`, and the default
`When.Always`.

## Transaction conditions

`NRedisStack` also supports a more extensive set of conditions that you
can add to transactions. They are implemented internally using
[`WATCH`]({{< relref "/commands/watch" >}}) commands in a way that is
guaranteed to be safe, without interactions between different clients.
Although conditions don't provide exactly the same behavior as
explicit `WATCH` commands, they are convenient to use and execute
efficiently.

The example below shows how to use the `AddCondition()` method on
a transaction to let it run only if a specified hash key does not
already exist. See
[Pipelines and transactions]({{< relref "/develop/clients/dotnet/transpipe" >}})
for more information about transactions.

<!--< clients-example pipe_trans_tutorial trans_watch "C#" >}}
< /clients-example >}} -->

```csharp
var watchedTrans = new Transaction(db);

watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

watchedTrans.Db.HashSetAsync(
"customer:39182",
new HashEntry[]{
new HashEntry("name", "David"),
new HashEntry("age", "27")
}
);

bool succeeded = watchedTrans.Execute();
Console.WriteLine(succeeded); // >>> true
```

The table below describes the full set of conditions you can add to
a transaction. Note that you can add more than one condition to the
same transaction if necessary.

| Condition | Description |
| :-- | :-- |
| `HashEqual` | Enforces that the given hash-field must have the specified value. |
| `HashExists` | Enforces that the given hash-field must exist. |
| `HashNotEqual` | Enforces that the given hash-field must not have the specified value. |
| `HashNotExists` | Enforces that the given hash-field must not exist. |
| `KeyExists` | Enforces that the given key must exist. |
| `KeyNotExists` | Enforces that the given key must not exist. |
| `ListIndexEqual` | Enforces that the given list index must have the specified value. |
| `ListIndexExists` | Enforces that the given list index must exist. |
| `ListIndexNotEqual` | Enforces that the given list index must not have the specified value. |
| `ListIndexNotExists` | Enforces that the given list index must not exist. |
| `StringEqual` | Enforces that the given key must have the specified value. |
| `StringNotEqual` | Enforces that the given key must not have the specified value. |
| `HashLengthEqual` | Enforces that the given hash length is a certain value. |
| `HashLengthLessThan` | Enforces that the given hash length is less than a certain value. |
| `HashLengthGreaterThan` | Enforces that the given hash length is greater than a certain value. |
| `StringLengthEqual` | Enforces that the given string length is a certain value. |
| `StringLengthLessThan` | Enforces that the given string length is less than a certain value. |
| `StringLengthGreaterThan` | Enforces that the given string length is greater than a certain value. |
| `ListLengthEqual` | Enforces that the given list length is a certain value. |
| `ListLengthLessThan` | Enforces that the given list length is less than a certain value. |
| `ListLengthGreaterThan` | Enforces that the given list length is greater than a certain value. |
| `SetLengthEqual` | Enforces that the given set cardinality is a certain value. |
| `SetLengthLessThan` | Enforces that the given set cardinality is less than a certain value. |
| `SetLengthGreaterThan` | Enforces that the given set cardinality is greater than a certain value. |
| `SetContains` | Enforces that the given set contains a certain member. |
| `SetNotContains` | Enforces that the given set does not contain a certain member. |
| `SortedSetLengthEqual` | Enforces that the given sorted set cardinality is a certain value. |
| `SortedSetLengthEqual` | Enforces that the given sorted set contains a certain number of members with scores in the given range. |
| `SortedSetLengthLessThan` | Enforces that the given sorted set cardinality is less than a certain value. |
| `SortedSetLengthLessThan` | Enforces that the given sorted set contains less than a certain number of members with scores in the given range. |
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set cardinality is greater than a certain value. |
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set contains more than a certain number of members with scores in the given range. |
| `SortedSetContains` | Enforces that the given sorted set contains a certain member. |
| `SortedSetNotContains` | Enforces that the given sorted set does not contain a certain member. |
| `SortedSetEqual` | Enforces that the given sorted set member must have the specified score. |
| `SortedSetNotEqual` | Enforces that the given sorted set member must not have the specified score. |
| `SortedSetScoreExists` | Enforces that the given sorted set must have the given score. |
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the given score. |
| `SortedSetScoreExists` | Enforces that the given sorted set must have the specified count of the given score. |
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the specified count of the given score. |
| `StreamLengthEqual` | Enforces that the given stream length is a certain value. |
| `StreamLengthLessThan` | Enforces that the given stream length is less than a certain value. |
| `StreamLengthGreaterThan` | Enforces that the given stream length is greater than a certain value. |
142 changes: 142 additions & 0 deletions content/develop/clients/dotnet/transpipe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
---
categories:
- docs
- develop
- stack
- oss
- rs
- rc
- oss
- kubernetes
- clients
description: Learn how to use Redis pipelines and transactions
linkTitle: Pipelines/transactions
title: Pipelines and transactions
weight: 5
---

Redis lets you send a sequence of commands to the server together in a batch.
There are two types of batch that you can use:

- **Pipelines** avoid network and processing overhead by sending several commands
to the server together in a single communication. The server then sends back
a single communication with all the responses. See the
[Pipelining]({{< relref "/develop/use/pipelining" >}}) page for more
information.
- **Transactions** guarantee that all the included commands will execute
to completion without being interrupted by commands from other clients.
See the [Transactions]({{< relref "/develop/interact/transactions" >}})
page for more information.

## Execute a pipeline

To execute commands in a pipeline, you first create a pipeline object
and then add commands to it using methods that resemble the *asynchronous*
versions of the standard command methods
(for example, `StringSetAsync()` and `StringGetAsync()`). The commands are
buffered in the pipeline and only execute when you call the `Execute()`
method on the pipeline object.

<!-- < clients-example pipe_trans_tutorial basic_pipe "C#" >}}
< /clients-example >}} -->

```csharp
var pipeline = new Pipeline(db);

for (int i = 0; i < 5; i++) {
pipeline.Db.StringSetAsync($"seat:{i}", $"#{i}");
}
pipeline.Execute();

var resp1 = db.StringGet("seat:0");
Console.WriteLine(resp1); // >>> #0

var resp2 = db.StringGet("seat:3");
Console.WriteLine(resp2); // >>> #3

var resp3 = db.StringGet("seat:4");
Console.WriteLine(resp2); // >>> #4
```

## Execute a transaction

A transaction works in a similar way to a pipeline. Create an
instance of the `Transaction` class, call async command methods
on that object, and then call the transaction object's
`Execute()` method to execute it.

<!-- < clients-example pipe_trans_tutorial basic_trans "C#" >}}
< /clients-example >}}-->

```csharp
var trans = new Transaction(db);

trans.Db.StringIncrementAsync("counter:1", 1);
trans.Db.StringIncrementAsync("counter:2", 2);
trans.Db.StringIncrementAsync("counter:3", 3);

trans.Execute();

var resp4 = db.StringGet("counter:1");
Console.WriteLine(resp4); // >>> 1

var resp5 = db.StringGet("counter:2");
Console.WriteLine(resp5); // >>> 2

var resp6 = db.StringGet("counter:3");
Console.WriteLine(resp6); // >>> 3
```

## Watch keys for changes

Redis supports *optimistic locking* to avoid inconsistent updates
to different keys. The basic idea is to watch for changes to any
keys that you use in a transaction while you are are processing the
updates. If the watched keys do change, you must restart the updates
with the latest data from the keys. See
[Transactions]({{< relref "/develop/interact/transactions" >}})
for more information about optimistic locking.

The approach to optimistic locking that other clients use
(adding the [`WATCH`]({{< relref "/commands/watch" >}}) command
explicitly to a transaction) doesn't work well with the
[multiplexing]({{< relref "/develop/clients/pools-and-muxing" >}})
system that `NRedisStack` uses.
Instead, `NRedisStack` relies on conditional execution of commands
to get a similar effect.

Use the `AddCondition()` method to abort a transaction if a particular
condition doesn't hold throughout its execution. If the transaction
does abort then the `Execute()` method returns a `false` value,
but otherwise returns `true`.

For example, the `KeyNotExists` condition aborts the transaction
if a specified key exists or is added by another client while the
transaction executes:

<!-- < clients-example pipe_trans_tutorial trans_watch "C#" >}}
< /clients-example >}} -->

```csharp
var watchedTrans = new Transaction(db);

watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));

watchedTrans.Db.HashSetAsync(
"customer:39182",
new HashEntry[]{
new HashEntry("name", "David"),
new HashEntry("age", "27")
}
);

bool succeeded = watchedTrans.Execute();
Console.WriteLine(succeeded); // >>> true
```

You can also use a `When` condition on certain individual commands to
specify that they only execute when a certain condition holds
(for example, the command does not change an existing key).
See
[Conditional execution]({{< relref "/develop/clients/dotnet/condexec" >}})
for a full description of transaction and command conditions.