Skip to content

Commit e50c491

Browse files
Merge pull request #1238 from redis/DOC-4560-csharp-trans-pipe
DOC-4560 added .NET trans/pipe and conditional execution pages
2 parents 9feb62f + 60039dc commit e50c491

File tree

2 files changed

+288
-0
lines changed

2 files changed

+288
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
categories:
3+
- docs
4+
- develop
5+
- stack
6+
- oss
7+
- rs
8+
- rc
9+
- oss
10+
- kubernetes
11+
- clients
12+
description: Understand how `NRedisStack` uses conditional execution
13+
linkTitle: Conditional execution
14+
title: Conditional execution
15+
weight: 6
16+
---
17+
18+
Most Redis client libraries use transactions with the
19+
[`WATCH`]({{< relref "/commands/watch" >}}) command as the main way to prevent
20+
two clients writing to the same key at once (see [Transactions]({{< relref "/develop/interact/transactions" >}}) for more information). Unfortunately, this approach is
21+
difficult to use explicitly in `NRedisStack`. Its
22+
[multiplexing]({{< relref "/develop/clients/pools-and-muxing" >}}) system
23+
is highly efficient and convenient but can also cause bad interactions
24+
when different connections use watched transactions at the same time.
25+
26+
Instead, `NRedisStack` relies more heavily on conditional execution. This comes
27+
in two basic forms, `When` conditions and transaction conditions, both of which
28+
are explained in the sections below.
29+
30+
## `When` conditions
31+
32+
Several commands have variants that only execute if the key they change
33+
already exists (or alternatively, if it doesn't already exist). For
34+
example, the [`SET`]({{< relref "/commands/set" >}}) command has the
35+
variants [`SETEX`]({{< relref "/commands/setex" >}}) (set when the key exists),
36+
and [`SETNX`]({{< relref "/commands/setnx" >}}) (set when the key doesn't exist).
37+
38+
Instead of providing the different variants of these commands, `NRedisStack`
39+
lets you add a `When` condition to the basic command to access its variants.
40+
The following example demonstrates this for the
41+
[`HashSet()`]({{< relref "/commands/hset" >}}) command.
42+
43+
<!-- < clients-example pipe_trans_tutorial when_condition "C#" >}}
44+
< /clients-example >}} -->
45+
46+
```csharp
47+
bool resp7 = db.HashSet("Details", "SerialNumber", "12345");
48+
Console.WriteLine(resp7); // >>> true
49+
50+
db.HashSet("Details", "SerialNumber", "12345A", When.NotExists);
51+
string resp8 = db.HashGet("Details", "SerialNumber");
52+
Console.WriteLine(resp8); // >>> 12345
53+
54+
db.HashSet("Details", "SerialNumber", "12345A");
55+
string resp9 = db.HashGet("Details", "SerialNumber");
56+
Console.WriteLine(resp9); // >>> 12345A
57+
```
58+
59+
The available conditions are `When.Exists`, `When.NotExists`, and the default
60+
`When.Always`.
61+
62+
## Transaction conditions
63+
64+
`NRedisStack` also supports a more extensive set of conditions that you
65+
can add to transactions. They are implemented internally using
66+
[`WATCH`]({{< relref "/commands/watch" >}}) commands in a way that is
67+
guaranteed to be safe, without interactions between different clients.
68+
Although conditions don't provide exactly the same behavior as
69+
explicit `WATCH` commands, they are convenient to use and execute
70+
efficiently.
71+
72+
The example below shows how to use the `AddCondition()` method on
73+
a transaction to let it run only if a specified hash key does not
74+
already exist. See
75+
[Pipelines and transactions]({{< relref "/develop/clients/dotnet/transpipe" >}})
76+
for more information about transactions.
77+
78+
<!--< clients-example pipe_trans_tutorial trans_watch "C#" >}}
79+
< /clients-example >}} -->
80+
81+
```csharp
82+
var watchedTrans = new Transaction(db);
83+
84+
watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));
85+
86+
watchedTrans.Db.HashSetAsync(
87+
"customer:39182",
88+
new HashEntry[]{
89+
new HashEntry("name", "David"),
90+
new HashEntry("age", "27")
91+
}
92+
);
93+
94+
bool succeeded = watchedTrans.Execute();
95+
Console.WriteLine(succeeded); // >>> true
96+
```
97+
98+
The table below describes the full set of conditions you can add to
99+
a transaction. Note that you can add more than one condition to the
100+
same transaction if necessary.
101+
102+
| Condition | Description |
103+
| :-- | :-- |
104+
| `HashEqual` | Enforces that the given hash-field must have the specified value. |
105+
| `HashExists` | Enforces that the given hash-field must exist. |
106+
| `HashNotEqual` | Enforces that the given hash-field must not have the specified value. |
107+
| `HashNotExists` | Enforces that the given hash-field must not exist. |
108+
| `KeyExists` | Enforces that the given key must exist. |
109+
| `KeyNotExists` | Enforces that the given key must not exist. |
110+
| `ListIndexEqual` | Enforces that the given list index must have the specified value. |
111+
| `ListIndexExists` | Enforces that the given list index must exist. |
112+
| `ListIndexNotEqual` | Enforces that the given list index must not have the specified value. |
113+
| `ListIndexNotExists` | Enforces that the given list index must not exist. |
114+
| `StringEqual` | Enforces that the given key must have the specified value. |
115+
| `StringNotEqual` | Enforces that the given key must not have the specified value. |
116+
| `HashLengthEqual` | Enforces that the given hash length is a certain value. |
117+
| `HashLengthLessThan` | Enforces that the given hash length is less than a certain value. |
118+
| `HashLengthGreaterThan` | Enforces that the given hash length is greater than a certain value. |
119+
| `StringLengthEqual` | Enforces that the given string length is a certain value. |
120+
| `StringLengthLessThan` | Enforces that the given string length is less than a certain value. |
121+
| `StringLengthGreaterThan` | Enforces that the given string length is greater than a certain value. |
122+
| `ListLengthEqual` | Enforces that the given list length is a certain value. |
123+
| `ListLengthLessThan` | Enforces that the given list length is less than a certain value. |
124+
| `ListLengthGreaterThan` | Enforces that the given list length is greater than a certain value. |
125+
| `SetLengthEqual` | Enforces that the given set cardinality is a certain value. |
126+
| `SetLengthLessThan` | Enforces that the given set cardinality is less than a certain value. |
127+
| `SetLengthGreaterThan` | Enforces that the given set cardinality is greater than a certain value. |
128+
| `SetContains` | Enforces that the given set contains a certain member. |
129+
| `SetNotContains` | Enforces that the given set does not contain a certain member. |
130+
| `SortedSetLengthEqual` | Enforces that the given sorted set cardinality is a certain value. |
131+
| `SortedSetLengthEqual` | Enforces that the given sorted set contains a certain number of members with scores in the given range. |
132+
| `SortedSetLengthLessThan` | Enforces that the given sorted set cardinality is less than a certain value. |
133+
| `SortedSetLengthLessThan` | Enforces that the given sorted set contains less than a certain number of members with scores in the given range. |
134+
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set cardinality is greater than a certain value. |
135+
| `SortedSetLengthGreaterThan` | Enforces that the given sorted set contains more than a certain number of members with scores in the given range. |
136+
| `SortedSetContains` | Enforces that the given sorted set contains a certain member. |
137+
| `SortedSetNotContains` | Enforces that the given sorted set does not contain a certain member. |
138+
| `SortedSetEqual` | Enforces that the given sorted set member must have the specified score. |
139+
| `SortedSetNotEqual` | Enforces that the given sorted set member must not have the specified score. |
140+
| `SortedSetScoreExists` | Enforces that the given sorted set must have the given score. |
141+
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the given score. |
142+
| `SortedSetScoreExists` | Enforces that the given sorted set must have the specified count of the given score. |
143+
| `SortedSetScoreNotExists` | Enforces that the given sorted set must not have the specified count of the given score. |
144+
| `StreamLengthEqual` | Enforces that the given stream length is a certain value. |
145+
| `StreamLengthLessThan` | Enforces that the given stream length is less than a certain value. |
146+
| `StreamLengthGreaterThan` | Enforces that the given stream length is greater than a certain value. |
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
categories:
3+
- docs
4+
- develop
5+
- stack
6+
- oss
7+
- rs
8+
- rc
9+
- oss
10+
- kubernetes
11+
- clients
12+
description: Learn how to use Redis pipelines and transactions
13+
linkTitle: Pipelines/transactions
14+
title: Pipelines and transactions
15+
weight: 5
16+
---
17+
18+
Redis lets you send a sequence of commands to the server together in a batch.
19+
There are two types of batch that you can use:
20+
21+
- **Pipelines** avoid network and processing overhead by sending several commands
22+
to the server together in a single communication. The server then sends back
23+
a single communication with all the responses. See the
24+
[Pipelining]({{< relref "/develop/use/pipelining" >}}) page for more
25+
information.
26+
- **Transactions** guarantee that all the included commands will execute
27+
to completion without being interrupted by commands from other clients.
28+
See the [Transactions]({{< relref "/develop/interact/transactions" >}})
29+
page for more information.
30+
31+
## Execute a pipeline
32+
33+
To execute commands in a pipeline, you first create a pipeline object
34+
and then add commands to it using methods that resemble the *asynchronous*
35+
versions of the standard command methods
36+
(for example, `StringSetAsync()` and `StringGetAsync()`). The commands are
37+
buffered in the pipeline and only execute when you call the `Execute()`
38+
method on the pipeline object.
39+
40+
<!-- < clients-example pipe_trans_tutorial basic_pipe "C#" >}}
41+
< /clients-example >}} -->
42+
43+
```csharp
44+
var pipeline = new Pipeline(db);
45+
46+
for (int i = 0; i < 5; i++) {
47+
pipeline.Db.StringSetAsync($"seat:{i}", $"#{i}");
48+
}
49+
pipeline.Execute();
50+
51+
var resp1 = db.StringGet("seat:0");
52+
Console.WriteLine(resp1); // >>> #0
53+
54+
var resp2 = db.StringGet("seat:3");
55+
Console.WriteLine(resp2); // >>> #3
56+
57+
var resp3 = db.StringGet("seat:4");
58+
Console.WriteLine(resp2); // >>> #4
59+
```
60+
61+
## Execute a transaction
62+
63+
A transaction works in a similar way to a pipeline. Create an
64+
instance of the `Transaction` class, call async command methods
65+
on that object, and then call the transaction object's
66+
`Execute()` method to execute it.
67+
68+
<!-- < clients-example pipe_trans_tutorial basic_trans "C#" >}}
69+
< /clients-example >}}-->
70+
71+
```csharp
72+
var trans = new Transaction(db);
73+
74+
trans.Db.StringIncrementAsync("counter:1", 1);
75+
trans.Db.StringIncrementAsync("counter:2", 2);
76+
trans.Db.StringIncrementAsync("counter:3", 3);
77+
78+
trans.Execute();
79+
80+
var resp4 = db.StringGet("counter:1");
81+
Console.WriteLine(resp4); // >>> 1
82+
83+
var resp5 = db.StringGet("counter:2");
84+
Console.WriteLine(resp5); // >>> 2
85+
86+
var resp6 = db.StringGet("counter:3");
87+
Console.WriteLine(resp6); // >>> 3
88+
```
89+
90+
## Watch keys for changes
91+
92+
Redis supports *optimistic locking* to avoid inconsistent updates
93+
to different keys. The basic idea is to watch for changes to any
94+
keys that you use in a transaction while you are are processing the
95+
updates. If the watched keys do change, you must restart the updates
96+
with the latest data from the keys. See
97+
[Transactions]({{< relref "/develop/interact/transactions" >}})
98+
for more information about optimistic locking.
99+
100+
The approach to optimistic locking that other clients use
101+
(adding the [`WATCH`]({{< relref "/commands/watch" >}}) command
102+
explicitly to a transaction) doesn't work well with the
103+
[multiplexing]({{< relref "/develop/clients/pools-and-muxing" >}})
104+
system that `NRedisStack` uses.
105+
Instead, `NRedisStack` relies on conditional execution of commands
106+
to get a similar effect.
107+
108+
Use the `AddCondition()` method to abort a transaction if a particular
109+
condition doesn't hold throughout its execution. If the transaction
110+
does abort then the `Execute()` method returns a `false` value,
111+
but otherwise returns `true`.
112+
113+
For example, the `KeyNotExists` condition aborts the transaction
114+
if a specified key exists or is added by another client while the
115+
transaction executes:
116+
117+
<!-- < clients-example pipe_trans_tutorial trans_watch "C#" >}}
118+
< /clients-example >}} -->
119+
120+
```csharp
121+
var watchedTrans = new Transaction(db);
122+
123+
watchedTrans.AddCondition(Condition.KeyNotExists("customer:39182"));
124+
125+
watchedTrans.Db.HashSetAsync(
126+
"customer:39182",
127+
new HashEntry[]{
128+
new HashEntry("name", "David"),
129+
new HashEntry("age", "27")
130+
}
131+
);
132+
133+
bool succeeded = watchedTrans.Execute();
134+
Console.WriteLine(succeeded); // >>> true
135+
```
136+
137+
You can also use a `When` condition on certain individual commands to
138+
specify that they only execute when a certain condition holds
139+
(for example, the command does not change an existing key).
140+
See
141+
[Conditional execution]({{< relref "/develop/clients/dotnet/condexec" >}})
142+
for a full description of transaction and command conditions.

0 commit comments

Comments
 (0)