diff --git a/docs/client-api/configuration/content/_conventions-csharp.mdx b/docs/client-api/configuration/content/_conventions-csharp.mdx
index ab53ffe3e1..1617d09230 100644
--- a/docs/client-api/configuration/content/_conventions-csharp.mdx
+++ b/docs/client-api/configuration/content/_conventions-csharp.mdx
@@ -41,6 +41,7 @@ import CodeBlock from '@theme/CodeBlock';
[MaxNumberOfRequestsPerSession](../../../client-api/configuration/conventions.mdx#maxnumberofrequestspersession)
[Modify serialization of property name](../../../client-api/configuration/conventions.mdx#modify-serialization-of-property-name)
[OperationStatusFetchMode](../../../client-api/configuration/conventions.mdx#operationstatusfetchmode)
+ [OptimisticConcurrencyMode](../../../client-api/configuration/conventions.mdx#optimisticconcurrencymode)
[PreserveDocumentPropertiesNotFoundOnModel](../../../client-api/configuration/conventions.mdx#preservedocumentpropertiesnotfoundonmodel)
[ReadBalanceBehavior](../../../client-api/configuration/conventions.mdx#readbalancebehavior)
[RequestTimeout](../../../client-api/configuration/conventions.mdx#requesttimeout)
@@ -54,7 +55,6 @@ import CodeBlock from '@theme/CodeBlock';
[UseHttpCompression](../../../client-api/configuration/conventions.mdx#usehttpcompression)
[UseHttpDecompression](../../../client-api/configuration/conventions.mdx#usehttpdecompression)
[HttpCompressionAlgorithm](../../../client-api/configuration/conventions.mdx#httpcompressionalgorithm)
- [UseOptimisticConcurrency](../../../client-api/configuration/conventions.mdx#useoptimisticconcurrency)
[WaitForIndexesAfterSaveChangesTimeout](../../../client-api/configuration/conventions.mdx#waitforindexesaftersavechangestimeout)
[WaitForNonStaleResultsTimeout](../../../client-api/configuration/conventions.mdx#waitfornonstaleresultstimeout)
[WaitForReplicationAfterSaveChangesTimeout](../../../client-api/configuration/conventions.mdx#waitforreplicationaftersavechangestimeout)
@@ -913,6 +913,40 @@ public OperationStatusFetchMode OperationStatusFetchMode \{ get; set; \}
+
+
+
+#### OptimisticConcurrencyMode
+
+* Use the `OptimisticConcurrencyMode` convention to set the default optimistic concurrency mode for ALL sessions opened under the document store.
+ Sessions that do not explicitly set `OptimisticConcurrencyMode` in their `SessionOptions` will inherit this value.
+
+* Learn more about optimistic concurrency modes and how to configure them in [Configure optimistic concurrency](../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx).
+
+* DEFAULT: `OptimisticConcurrencyMode.None`
+
+
+```csharp
+// Syntax:
+public OptimisticConcurrencyMode OptimisticConcurrencyMode { get; set; }
+
+// Available modes:
+public enum OptimisticConcurrencyMode
+{
+ None, // Default - no concurrency checks (Last Write Wins)
+ Writes, // Checks modified documents only
+ WritesAndReads // Checks all tracked documents (modified and not modified)
+}
+```
+
+
+
+ The `UseOptimisticConcurrency` boolean convention is obsolete and will be removed in a future major version.
+ Use `OptimisticConcurrencyMode` instead.
+
+
+
+
#### PreserveDocumentPropertiesNotFoundOnModel
@@ -1163,28 +1197,6 @@ public HttpCompressionAlgorithm HttpCompressionAlgorithm \{ get; set; \}
-
-
-
-#### UseOptimisticConcurrency
-* When setting the `UseOptimisticConcurrency` convention to `true`,
- Optimistic Concurrency checks will be applied for all sessions opened from the Document Store.
-
-* Learn more about Optimistic Concurrency and the various ways to enable it in the
- [how to enable optimistic concurrency](../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx)
- article.
-
-* DEFAULT: `false`
-
-
-
-{`// Syntax:
-public bool UseOptimisticConcurrency \{ get; set; \}
-`}
-
-
-
-
#### WaitForIndexesAfterSaveChangesTimeout
diff --git a/docs/client-api/session/cluster-transaction/content/_overview-csharp.mdx b/docs/client-api/session/cluster-transaction/content/_overview-csharp.mdx
index 96e5ebf3a3..6a8e5b82cb 100644
--- a/docs/client-api/session/cluster-transaction/content/_overview-csharp.mdx
+++ b/docs/client-api/session/cluster-transaction/content/_overview-csharp.mdx
@@ -17,6 +17,7 @@ import CodeBlock from '@theme/CodeBlock';
* [Cluster-wide transaction vs. Single-node transaction](../../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction)
+
## Open a cluster transaction
* To work with a cluster transaction open a **cluster-wide session**,
@@ -24,8 +25,8 @@ import CodeBlock from '@theme/CodeBlock';
-
-{`using (var session = store.OpenSession(new SessionOptions
+```csharp">
+using (var session = store.OpenSession(new SessionOptions
{
// Set mode to be cluster-wide
TransactionMode = TransactionMode.ClusterWide
@@ -34,12 +35,11 @@ import CodeBlock from '@theme/CodeBlock';
// * Mode is not specified
// * Explicitly set TransactionMode.SingleNode
}))
-`}
-
+```
-
-{`using (var session = store.OpenAsyncSession(new SessionOptions
+```csharp
+using (var session = store.OpenAsyncSession(new SessionOptions
{
// Set mode to be cluster-wide
TransactionMode = TransactionMode.ClusterWide
@@ -48,39 +48,41 @@ import CodeBlock from '@theme/CodeBlock';
// * Mode is not specified
// * Explicitly set TransactionMode.SingleNode
}))
-`}
-
+```
* Similar to the single-node session,
any CRUD operations can be made on the cluster-wide session and the session will track them as usual.
-
-
## Cluster-wide transaction vs. Single-node transaction
-#### Cluster-Wide
-* Cluster-wide transactions are **fully ACID** transactions across all the database-group nodes.
+
+### Cluster-Wide
+
+* **ACID compliance**:
+ Cluster-wide transactions are fully ACID transactions across all the database-group nodes.
Implemented by the Raft algorithm, the cluster must first reach a consensus.
- Once the majority of the nodes have approved the transaction,
+ Once the majority of the nodes have approved the transaction,
the transaction is registered for execution in the transaction queue of all nodes in an atomic fashion.
-* The transaction will either **succeed on all nodes or be rolled-back**.
+* **All-or-nothing execution**:
+ The transaction will either succeed on all nodes or be rolled-back.
* The transaction is considered successful only when successfully registered on all the database-group nodes.
Once executed on all nodes, the data is consistent and available on all nodes.
* A failure to register the transaction on any node will cause the transaction to roll-back on all nodes and changes will Not be applied.
-* The only **actions available** are:
+* **Available actions**:
+ The only actions available are:
* PUT / DELETE a document
* PUT / DELETE a compare-exchange item
-* To prevent from concurrent documents modifications,
- the server creates [Atomic-Guards](../../../../compare-exchange/atomic-guards.mdx) that will be associated with the documents.
+* **Conflict-free**:
+ Cluster-wide transactions are conflict-free. To prevent from concurrent documents modifications,
+ the server creates [Atomic-Guards](../../../../compare-exchange/atomic-guards.mdx) that will be associated with the documents.
An Atomic-Guard will be created when:
* A new document is created
* Modifying an existing document that doesn't have yet an Atomic-Guard
-* Cluster-wide transactions are **conflict-free**.
-* The cluster-wide transaction is considered **more expensive and less performant**
- since a cluster consensus is required prior to execution.
+* **Performance**:
+ The cluster-wide transaction is considered more expensive and less performant since a cluster consensus is required prior to execution.
* **Prefer a cluster-wide transaction when**:
* Prioritizing consistency over performance & availability
* When you would rather fail if a successful operation on all nodes cannot be ensured
@@ -88,16 +90,24 @@ import CodeBlock from '@theme/CodeBlock';
-#### Single-Node
-* A single-node transaction is considered successful once executed successfully on the node the client is communicating with.
- The data is **immediately available** on that node, and it will be **eventually-consistent** across all the other database nodes when the replication process takes place soon after.
-* **Any action is available** except for PUT / DELETE a compare-exchange item.
+
+### Single-Node
+
+* **Execution and availability**:
+ A single-node transaction is considered successful once executed successfully on the node the client is communicating with.
+ The data is immediately available on that node, and it will be **eventually-consistent** across all the other database nodes when the replication process takes place soon after.
+* **Available actions**:
+ Any action is available except for PUT / DELETE a compare-exchange item.
No Atomic-Guards are created by the server.
-* **Conflicts** may occur when two concurrent transactions modify the same document on different nodes at the same time.
+* **Conflicts**:
+ Conflicts may occur when two concurrent transactions modify the same document on different nodes at the same time.
They are resolved according to the defined conflict settings, either by using the latest version (default) or by following a conflict resolution script.
Revisions are created for the conflicting documents so that any document can be recovered.
-* The single-node transaction is considered **faster and less expensive**,
- as no cluster consensus is required for its execution.
+* **Optimistic concurrency**:
+ Single-node sessions support optional [Optimistic concurrency checks](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx).
+ By default, no checks are performed (Last Write Wins), but you can configure the session to detect and reject conflicting writes.
+* **Performance**:
+ The single-node transaction is considered faster and less expensive, as no cluster consensus is required for its execution.
* **Prefer a single-node transaction when**:
* Prioritizing performance & availability over consistency
* When immediate data persistence is crucial
@@ -110,8 +120,4 @@ import CodeBlock from '@theme/CodeBlock';
For a detailed description of transactions in RavenDB please refer to the [Transaction support in RavenDB](../../../../client-api/faq/transaction-support.mdx) article.
-
-
-
-
-
+
\ No newline at end of file
diff --git a/docs/client-api/session/configuration/content/_how-to-disable-tracking-csharp.mdx b/docs/client-api/session/configuration/content/_how-to-disable-tracking-csharp.mdx
index 20b9c2afeb..eedeb68d66 100644
--- a/docs/client-api/session/configuration/content/_how-to-disable-tracking-csharp.mdx
+++ b/docs/client-api/session/configuration/content/_how-to-disable-tracking-csharp.mdx
@@ -2,6 +2,8 @@ import Admonition from '@theme/Admonition';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
+import Panel from "@site/src/components/Panel";
+import ContentFrame from "@site/src/components/ContentFrame";
@@ -16,10 +18,12 @@ import CodeBlock from '@theme/CodeBlock';
* [Disable tracking all entities in session](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#disable-tracking-all-entities-in-session)
* [Disable tracking query results](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#disable-tracking-query-results)
* [Customize tracking in conventions](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#customize-tracking-in-conventions)
- * [Using 'Include' in a NoTracking session will throw](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#using-)
+ * [Using 'Include' in a NoTracking session will throw](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#using-include-in-a-notracking-session-will-throw)
+ * [Using optimistic concurrency modes in a NoTracking session will throw](../../../../client-api/session/configuration/how-to-disable-tracking.mdx#using-optimistic-concurrency-modes-in-a-notracking-session-will-throw)
-## Disable tracking changes for a specific entity
+
+
* You can prevent the session from persisting changes made to a specific entity by using `IgnoreChangesFor`.
* Once changes are ignored for the entity:
@@ -31,8 +35,8 @@ import CodeBlock from '@theme/CodeBlock';
-
-{`// Load a product entity - the session will track the entity by default
+```csharp
+// Load a product entity - the session will track the entity by default
Product product = session.Load("products/1-A");
// Call 'IgnoreChangesFor' to instruct the session to ignore changes made to this entity
@@ -42,12 +46,11 @@ session.Advanced.IgnoreChangesFor(product);
product.UnitsInStock += 1;
session.SaveChanges();
-`}
-
+```
-
-{`// Load a product entity - the session will track the entity by default
+```csharp
+// Load a product entity - the session will track the entity by default
Product product = await asyncSession.LoadAsync("products/1-A");
// Call 'IgnoreChangesFor' to instruct the session to ignore changes made to this entity
@@ -57,27 +60,25 @@ asyncSession.Advanced.IgnoreChangesFor(product);
product.UnitsInStock += 1;
await asyncSession.SaveChangesAsync();
-`}
-
+```
**Syntax**
-
-{`void IgnoreChangesFor(object entity);
-`}
-
+```csharp
+void IgnoreChangesFor(object entity);
+```
| Parameter | Type | Description |
|------------|----------|------------------------------------------------------|
| **entity** | `object` | Instance of entity for which changes will be ignored |
+
-
-## Disable tracking all entities in session
+
* Tracking can be disabled for all entities in the session's options.
* When tracking is disabled for the session:
@@ -86,8 +87,8 @@ await asyncSession.SaveChangesAsync();
-
-{`using (IDocumentSession session = store.OpenSession(new SessionOptions
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
{
// Disable tracking for all entities in the session's options
NoTracking = true
@@ -102,12 +103,11 @@ await asyncSession.SaveChangesAsync();
// Entities instances are not the same
Assert.NotEqual(employee1, employee2);
}
-`}
-
+```
-
-{`using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
{
// Disable tracking for all entities in the session's options
NoTracking = true
@@ -122,21 +122,20 @@ await asyncSession.SaveChangesAsync();
// Entities instances are not the same
Assert.NotEqual(employee1, employee2);
}
-`}
-
+```
+
-
-## Disable tracking query results
+
* Tracking can be disabled for all entities resulting from a query.
-
-
-{`using (IDocumentSession session = store.OpenSession())
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
{
// Define a query
List employeesResults = session.Query()
@@ -152,12 +151,11 @@ await asyncSession.SaveChangesAsync();
// Change to 'firstEmployee' will not be persisted
session.SaveChanges();
}
-`}
-
+```
-
-
-{`using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Define a query
List employeesResults = asyncSession.Query()
@@ -173,12 +171,11 @@ await asyncSession.SaveChangesAsync();
// Change to 'firstEmployee' will not be persisted
await asyncSession.SaveChangesAsync();
}
-`}
-
+```
-
-
-{`using (IDocumentSession session = store.OpenSession())
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
{
// Define a query
List employeesResults = session.Advanced.DocumentQuery()
@@ -194,12 +191,11 @@ await asyncSession.SaveChangesAsync();
// Change to 'firstEmployee' will not be persisted
session.SaveChanges();
}
-`}
-
+```
-
-
-{`using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Define a query
List employeesResults = asyncSession.Advanced.AsyncDocumentQuery()
@@ -215,14 +211,13 @@ await asyncSession.SaveChangesAsync();
// Change to 'firstEmployee' will not be persisted
await asyncSession.SaveChangesAsync();
}
-`}
-
+```
+
-
-## Customize tracking in conventions
+
* You can further customize and fine-tune which entities will not be tracked
by configuring the `ShouldIgnoreEntityChanges` convention method on the document store.
@@ -230,25 +225,25 @@ await asyncSession.SaveChangesAsync();
**Example**
-
-
-{`using (var store = new DocumentStore()
-\{
+
+```csharp
+using (var store = new DocumentStore()
+{
// Define the 'ignore' convention on your document store
Conventions =
- \{
+ {
ShouldIgnoreEntityChanges =
// Define for which entities tracking should be disabled
// Tracking will be disabled ONLY for entities of type Employee whose FirstName is Bob
(session, entity, id) => (entity is Employee e) &&
(e.FirstName == "Bob")
- \}
-\}.Initialize())
-\{
+ }
+}.Initialize())
+{
using (IDocumentSession session = store.OpenSession())
- \{
- var employee1 = new Employee \{ Id = "employees/1", FirstName = "Alice" \};
- var employee2 = new Employee \{ Id = "employees/2", FirstName = "Bob" \};
+ {
+ var employee1 = new Employee { Id = "employees/1", FirstName = "Alice" };
+ var employee2 = new Employee { Id = "employees/2", FirstName = "Bob" };
session.Store(employee1); // This entity will be tracked
session.Store(employee2); // Changes to this entity will be ignored
@@ -259,23 +254,17 @@ await asyncSession.SaveChangesAsync();
employee2.FirstName = "Alice"; // This entity will now be tracked
session.SaveChanges(); // Only employee2 is persisted
- \}
-\}
-`}
-
+ }
+}
+```
-
-
**Syntax**
-
-
-{`public Func ShouldIgnoreEntityChanges;
-`}
-
+
+```csharp
+public Func ShouldIgnoreEntityChanges;
+```
| Parameter | Description |
@@ -288,9 +277,9 @@ await asyncSession.SaveChangesAsync();
|--------------|-------------------------------------------------------------------------|
| `bool` | `true` - Entity will Not be tracked
`false` - Entity will be tracked |
+
-
-## Using 'Include' in a NoTracking session will throw
+
* Attempting to use `Include` in a `NoTracking` session will throw an exception.
@@ -300,12 +289,13 @@ await asyncSession.SaveChangesAsync();
* This applies to all items that can be included -
e.g., documents, compare-exchange items, counters, revisions, and time series.
+
**Include when loading**:
-
-{`using (IDocumentSession session = store.OpenSession(new SessionOptions
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
{
// Working with a non-tracking session
NoTracking = true
@@ -327,12 +317,11 @@ await asyncSession.SaveChangesAsync();
// An InvalidOperationException is expected here
}
}
-`}
-
+```
-
-{`using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
{
// Working with a non-tracking session
NoTracking = true
@@ -354,8 +343,7 @@ await asyncSession.SaveChangesAsync();
// An InvalidOperationException is expected here
}
}
-`}
-
+```
@@ -363,8 +351,8 @@ await asyncSession.SaveChangesAsync();
-
-{`using (IDocumentSession session = store.OpenSession(new SessionOptions
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
{
// Working with a non-tracking session
NoTracking = true
@@ -383,12 +371,11 @@ await asyncSession.SaveChangesAsync();
// An InvalidOperationException is expected here
}
}
-`}
-
+```
-
-{`using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
{
// Working with a non-tracking session
NoTracking = true
@@ -407,11 +394,17 @@ await asyncSession.SaveChangesAsync();
// An InvalidOperationException is expected here
}
}
-`}
-
+```
+
+
+* When `NoTracking` is enabled, the session does not track documents or retain their change vectors.
+ As a result, setting `OptimisticConcurrencyMode` to `Writes` or `WritesAndReads` is not permitted and will throw an exception.
+* Learn more in [Configure optimistic concurrency - Constraints](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#constraints).
+
+
\ No newline at end of file
diff --git a/docs/client-api/session/configuration/content/_how-to-enable-optimistic-concurrency-csharp.mdx b/docs/client-api/session/configuration/content/_how-to-enable-optimistic-concurrency-csharp.mdx
index 63ca8c90a4..20fd7c0403 100644
--- a/docs/client-api/session/configuration/content/_how-to-enable-optimistic-concurrency-csharp.mdx
+++ b/docs/client-api/session/configuration/content/_how-to-enable-optimistic-concurrency-csharp.mdx
@@ -2,150 +2,588 @@ import Admonition from '@theme/Admonition';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
+import Panel from "@site/src/components/Panel";
+import ContentFrame from "@site/src/components/ContentFrame";
-* By default, optimistic concurrency checks are **disabled**. Changes made outside of the session object will be overwritten.
- Concurrent changes to the same document will use the _Last Write Wins_ strategy so a lost update anomaly is possible
- with the default configuration of the [session](../../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx).
-
-* Optimistic concurrency can be **enabled** for:
- * A specific document
- * A specific session (enable on a per-session basis)
- * All sessions (enable globally, at the document store level)
+* This article explains **optimistic concurrency** in RavenDB,
+ focusing on how optimistic concurrency behaves in sessions operating with [single-node transactions](../../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) and how to configure it.
+
+* By default, single-node sessions do not perform concurrency checks,
+ so concurrent updates to the same document follow the "Last Write Wins" strategy, which may lead to lost updates.
+
+* Enabling optimistic concurrency ensures RavenDB detects and prevents conflicting updates,
+ preserving data integrity by rejecting changes when a document has been modified by another session.
-* With optimistic concurrency enabled, RavenDB will generate a concurrency exception (and abort all modifications in
- the current transaction) when trying to save a document that has been modified on the server side after the client
- loaded and modified it.
+* RavenDB supports three [modes of optimistic concurrency](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#optimistic-concurrency-modes),
+ allowing you to choose a level of conflict validation that suits your application.
+
+* The optimistic concurrency mode can be configured at different scopes:
+ * Globally, for all sessions under a document store.
+ * Per session, to apply it selectively.
+ * Per document, to override the session-level settings.
-* The `ConcurrencyException` that might be thrown upon the `SaveChanges` call needs to be handled by the caller.
- The operation can be retried (the document needs to be reloaded since it got changed meanwhile) or handle the error
- in a way that is suitable in a given scenario.
+---
+
+* In this article:
+ * [Optimistic concurrency - Overview](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#optimistic-concurrency-overview)
+ * [Default concurrency behavior with single-node transactions](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#default-concurrency-behavior-with-single-node-transactions)
+ * [Optimistic concurrency with single-node transactions](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#optimistic-concurrency-with-single-node-transactions)
+ * [Concurrency control in cluster-wide transactions](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#concurrency-control-in-cluster-wide-transactions)
+ * [Optimistic concurrency - Modes](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#optimistic-concurrency-modes)
+ * [Configure globally](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#configure-globally)
+ * [Configure per session](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#configure-per-session)
+ * [Disable for specific document (when enabled on the session)](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#disable-for-specific-document-when-enabled-on-session)
+ * [Enable for specific document (when disabled on the session)](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#enable-for-specific-document-when-disabled-on-session)
+ * [Constraints](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#constraints)
+ * [Syntax](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#syntax)
-* In this page:
- * [Enable for specific session](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#enable-for-specific-session)
- * [Enable globally](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#enable-globally)
- * [Disable for specific document (when enabled on session)](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#disable-for-specific-document-(when-enabled-on-session))
- * [Enable for specific document (when disabled on session)](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#enable-for-specific-document-(when-disabled-on-session))
+
-
+
+
+* A session in RavenDB typically follows these steps:
+
+
+
+ 1. **Load or query** for documents from the database.
+ Unless [Tracking is explicitly disabled](../../../../client-api/session/configuration/how-to-disable-tracking.mdx), these documents are automatically [Tracked by the session](../../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#unit-of-work-pattern).
+ When tracked, the session stores the document's [Change vector](../../../../server/clustering/replication/change-vector.mdx) (a unique version identifier) in memory.
+
+
+ Tracking applies to:
+ * **Loaded documents**:
+ Retrieved explicitly using methods like `session.Load()` or `session.LoadStartingWith()`.
+ * **Queried documents**:
+ Returned as part of query results using `session.Query()`.
+ * **Included documents**:
+ Retrieved as part of an `Include()` clause, where related documents are included alongside results.
+ * **Created documents**:
+ Stored in the session via `session.Store()`.
+ Note: Created documents are tracked but have no change vector until saved to the database.
+ On save, the server verifies that no document with the same ID already exists.
+
+
+ 2. **Modify documents** in memory, such as allowing the user to edit them via application forms.
+
+ 3. **Save changes** with `SaveChanges()`, persisting updates to the database.
+
+
+
+* Because these actions span **multiple requests** (loading/querying happens separately from saving),
+ another session may modify the same documents during this time - between when you loaded the document into your session and when the changes are saved.
+
+* This can cause your changes to unintentionally overwrite newer updates, resulting in a lost update.
+ **Optimistic concurrency** offers a mechanism to detect and prevent conflicts, ensuring your changes are only saved if the document hasn’t been modified elsewhere since it was loaded by your session.
-* Note that the `UseOptimisticConcurrency` setting only applies to documents that have been modified by the current session.
- E.g., if you load documents `users/1-A` and `users/2-A` in a session, make modifications only to `users/1-A`, and then call `SaveChanges`,
- the operation will succeed regardless of the optimistic concurrency setting, even if `users/2-A` has been changed by another process in the meantime.
+---
+
+
+
+### Default concurrency behavior with single-node transactions
+
+By default, sessions using [single-node transactions](../../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) do not perform optimistic concurrency checks:
+
+* **Silent overwrites**:
+ Documents are saved without validating whether the version loaded by the session is still current,
+ allowing updates to be applied unconditionally.
+
+* **Last write wins**:
+ If two sessions modify the same document, the last `SaveChanges()` call overwrites earlier changes.
+
+* **Suitability**:
+ This approach works well when overwriting changes is acceptable,
+ but it can cause lost updates if concurrent modifications go undetected.
-* However, if you modify both documents and attempt to save changes with optimistic concurrency enabled, an exception will be raised
- if `users/2-A` has been modified externally.
- In this case, the updates to both `users/1-A` and `users/2-A` will be cancelled.
+
+
+
+
+### Optimistic concurrency with single-node transactions
-
+To prevent lost updates and conflicting writes,
+sessions operating with single-node transactions can enable optimistic concurrency checks:
-
+* **Change vector validation**:
+ When `SaveChanges()` is called, the session sends the document’s change vector to the server.
+ The server compares the change vector stored in the database against the one provided by your session to determine whether the document has been modified by another client or session since it was loaded into your session.
-A detailed description of transactions and concurrency control in RavenDB is available here:
-[Transaction support in RavenDB](../../../../client-api/faq/transaction-support.mdx)
+* **Conflict detection**:
+ If the document has been modified by another session/client during this time, RavenDB rejects the operation and raises a `ConcurrencyException`.
+ This gives your application control over how to handle the conflict - whether by reloading the document, merging the changes, or canceling the operation.
+
+* **Optimistic concurrency modes**:
+ Optimistic concurrency can be configured to validate only modified documents or both modified and read-only documents.
+ Learn more in the [Optimistic concurrency modes](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#optimistic-concurrency-modes) section below.
+
+
+
+### Concurrency control in cluster-wide transactions
+
+When the session operates in [Cluster-wide mode](../../../../client-api/session/cluster-transaction/overview.mdx),
+concurrency control is managed by the cluster transaction mechanism:
+
+* RavenDB coordinates cluster-wide writes through the Raft protocol and validates document changes as part of the transaction process.
+ To ensure consistency across the cluster, document writes use [Atomic guards](../../../../compare-exchange/atomic-guards.mdx).
+ If another cluster-wide transaction modifies the same document first, `SaveChanges()` will fail with a `ConcurrencyException`, allowing your application to handle the conflict.
+
+* For more information, see [Cluster Transaction - Overview](../../../../client-api/session/cluster-transaction/overview.mdx).
-## Enable for specific session
+
+
-
-
-{`using (IDocumentSession session = store.OpenSession())
-\{
- // Enable optimistic concurrency for this session
- session.Advanced.UseOptimisticConcurrency = true;
+
- // Save a document in this session
- Product product = new Product \{ Name = "Some Name" \};
+* When working with a session that uses [Single-node transactions](../../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction),
+ you can configure optimistic concurrency to determine whether the server validates document versions and raises exceptions for conflicting updates.
+
+* RavenDB offers three modes, each providing a different level of conflict protection.
+
+---
+
+
+
+### `None` (Default behavior, Last write wins)
+
+In the default mode, `OptimisticConcurrencyMode.None`, no version checks are performed.
+
+ * **Unconditional saves**:
+ Each PUT and DELETE operation sent during `SaveChanges()` is transmitted without a change vector,
+ allowing the server to apply changes unconditionally.
+
+ * **Last write wins**:
+ If two sessions modify the same document and both call `SaveChanges()`,
+ the most recent save silently overwrites earlier changes, potentially resulting in a lost update.
+
+Choose this mode when:
+ * Write conflicts are rare or acceptable.
+ * High throughput is more important than enforcing strict concurrency checks.
+
+
+
+
+
+### `Writes`
+
+When using `OptimisticConcurrencyMode.Writes`,
+the session performs version checks on all **modified** documents:
+
+* **Change vector inclusion**:
+ The session sends the document’s change vector with all PUT and DELETE operations during `SaveChanges()`.
+
+* **Conflict Detection**:
+ The server compares the change vector sent by the session with the document's current version in the database.
+ If the document has been modified by another session in the meantime, the server rejects the operation and raises a `ConcurrencyException`.
+
+* **Protection against overwrites**:
+ Changes are persisted only if the document's change vector matches the version it had when it was loaded into your session, ensuring no unintended overwrites occur.
+
+This mode is ideal for most scenarios requiring write protection as it:
+ * Detects and prevents conflicting updates effectively.
+ * Maintains good performance by not sending change vectors for documents that were loaded but not modified in the session.
+
+
+
+
+
+### `WritesAndReads`
+
+When using `OptimisticConcurrencyMode.WritesAndReads`,
+the session extends the behavior of _Writes_ to include ALL tracked documents, even those that were **not modified**.
+
+* **Change vector inclusion**:
+ During `SaveChanges()`, the session sends the change vector of ALL tracked documents to the server,
+ including both modified and unmodified documents.
+
+* **Conflict Detection**:
+ If the server detects that a tracked document - whether modified or not - has been updated by another session,
+ it rejects the save operation and throws a `ConcurrencyException`. This prevents unintended reliance on outdated read-only documents.
+
+* **Strict Consistency**:
+ This mode ensures consistency across all documents tracked by the session, protecting against subtle issues caused by documents that were only "read" in your session but were modified externally.
+
+Use this mode in scenarios where:
+ * The correctness of business operations relies on the state of read-only documents.
+ * Strict protection against concurrent changes is required for both modified and unmodified documents.
+ * Consistency and correctness are prioritized over performance.
+
+
+
+
+
+
+
+* Use the [store.Conventions.OptimisticConcurrencyMode](../../../../client-api/configuration/conventions.mdx#optimisticconcurrencymode) convention
+ to set the default optimistic concurrency mode for ALL sessions created under the same document store.
+
+* You can override this setting for individual sessions if needed. See [Configure per session](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#configure-per-session) for more details.
+
+
+```csharp
+// Set optimistic concurrency mode for all sessions opened under this document store
+// Set to 'Writes' mode
+store.Conventions.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
+
+// Sessions that do not set an explicit mode will inherit from the conventions
+using (IDocumentSession session = store.OpenSession())
+{
+ OptimisticConcurrencyMode mode = session.Advanced.OptimisticConcurrencyMode;
+ // returns 'Writes'
+}
+```
+
+
+
+
+
+
+* Optimistic concurrency can be configured for a specific session,
+ overriding the mode inherited from the document store conventions.
+
+* Two ways to set the mode for a session:
+ * Set `OptimisticConcurrencyMode` via `SessionOptions` when opening the session.
+ * Set `session.Advanced.OptimisticConcurrencyMode` on an already-opened session.
+
+---
+
+### Setting mode to `Writes`
+
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ // Set to 'Writes' mode in the session options
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
+}))
+{
+ // Store a new document and save it
+ // On save, the server verifies that no document with that ID already exists
+ Product product = new Product { Name = "Some Name" };
session.Store(product, "products/999");
session.SaveChanges();
- // Modify the document 'externally' by another session
+ // The document is modified externally by another session
using (IDocumentSession otherSession = store.OpenSession())
- \{
+ {
Product otherProduct = otherSession.Load("products/999");
otherProduct.Name = "Other Name";
otherSession.SaveChanges();
- \}
+ }
- // Trying to modify the document without reloading it first will throw
+ // Attempting to save 'products/999' without reloading
+ // will throw a 'ConcurrencyException' because the document was modified
+ // by another session since it was last saved in this session.
product.Name = "Better Name";
- session.SaveChanges(); // This will throw a ConcurrencyException
-\}
-`}
-
+ session.SaveChanges(); // Throws 'ConcurrencyException'
+}
+```
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
+{
+ // Set to 'Writes' mode in the session options
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
+}))
+{
+ // Store a new document and save it
+ // On save, the server verifies that no document with that ID already exists
+ Product product = new Product { Name = "Some Name" };
+ await asyncSession.StoreAsync(product, "products/999");
+ await asyncSession.SaveChangesAsync();
+
+ // The document is modified externally by another session
+ using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
+ {
+ Product otherProduct = await otherAsyncSession.LoadAsync("products/999");
+ otherProduct.Name = "Other Name";
+ await otherAsyncSession.SaveChangesAsync();
+ }
-
-
-* Enabling optimistic concurrency in a session will ensure that changes made to a document will only be persisted
- if the version of the document sent in the `SaveChanges()` call matches its version from the time it was initially read (loaded from the server).
-
-* Note that it's necessary to enable optimistic concurrency for ALL sessions that modify the documents for which you want to guarantee that no writes will be silently discarded.
- If optimistic concurrency is enabled in some sessions but not in others, and they modify the same documents, the risk of the lost update anomaly still exists.
-
-
-
-
-
-## Enable globally
+ // Attempting to save 'products/999' without reloading
+ // will throw a 'ConcurrencyException' because the document was modified
+ // by another session since it was last saved in this session.
+ product.Name = "Better Name";
+ await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
+}
+```
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IDocumentSession session = store.OpenSession())
+{
+ // Set to 'Writes' mode in the 'session.Advanced' properties
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
-* Optimistic concurrency can also be _enabled_ for all sessions that are opened under a document store.
+ // Store a new document and save it
+ // On save, the server verifies that no document with that ID already exists
+ Product product = new Product { Name = "Some Name" };
+ session.Store(product, "products/999");
+ session.SaveChanges();
-* Use the [store.Conventions.UseOptimisticConcurrency](../../../../client-api/configuration/conventions.mdx#useoptimisticconcurrency) convention to enable globally.
+ // The document is modified externally by another session
+ using (IDocumentSession otherSession = store.OpenSession())
+ {
+ Product otherProduct = otherSession.Load("products/999");
+ otherProduct.Name = "Other Name";
+ otherSession.SaveChanges();
+ }
-
-
-{`// Enable for all sessions that will be opened within this document store
-store.Conventions.UseOptimisticConcurrency = true;
+ // Attempting to save 'products/999' without reloading
+ // will throw a 'ConcurrencyException' because the document was modified
+ // by another session since it was last saved in this session.
+ product.Name = "Better Name";
+ session.SaveChanges(); // Throws 'ConcurrencyException'
+}
+```
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Set to 'Writes' mode in the 'session.Advanced' properties
+ asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
+
+ // Store a new document and save it
+ // On save, the server verifies that no document with that ID already exists
+ Product product = new Product { Name = "Some Name" };
+ await asyncSession.StoreAsync(product, "products/999");
+ await asyncSession.SaveChangesAsync();
+
+ // The document is modified externally by another session
+ using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
+ {
+ Product otherProduct = await otherAsyncSession.LoadAsync("products/999");
+ otherProduct.Name = "Other Name";
+ await otherAsyncSession.SaveChangesAsync();
+ }
-using (IDocumentSession session = store.OpenSession())
-\{
- bool isSessionUsingOptimisticConcurrency = session.Advanced.UseOptimisticConcurrency; // will return true
-\}
-`}
-
+ // Attempting to save 'products/999' without reloading
+ // will throw a 'ConcurrencyException' because the document was modified
+ // by another session since it was last saved in this session.
+ product.Name = "Better Name";
+ await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
+}
+```
+
+
+
+---
+
+### Setting mode to `WritesAndReads`
+
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ // 'WritesAndReads' checks all tracked documents, both modified and unmodified
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads
+}))
+{
+ // Load documents, the session will track them
+
+ // 'products/999' is tracked, will Not be modified
+ Product readOnlyProduct = session.Load("products/999");
+
+ // 'products/111' is tracked, will be modified
+ Product productToUpdate = session.Load("products/111")
+
+ productToUpdate.Name = "Updated Name";
+
+ // Document 'products/999' is modified externally by another session
+ using (IDocumentSession otherSession = store.OpenSession())
+ {
+ Product otherProduct = otherSession.Load("products/999");
+ otherProduct.Name = "Other Name";
+ otherSession.SaveChanges();
+ }
+
+ // Attempting to save will throw a 'ConcurrencyException'
+ // because even though 'products/999' was Not modified in this session,
+ // it was modified by another session since it was loaded.
+ session.SaveChanges(); // Throws 'ConcurrencyException'
+}
+```
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession(new SessionOptions
+{
+ // 'WritesAndReads' also checks unmodified tracked documents
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads
+}))
+{
+ // Load documents, the session will track them
+
+ // 'products/999' is tracked, will Not be modified
+ Product readOnlyProduct = await asyncSession.LoadAsync("products/999");
+
+ // 'products/111' is tracked, will be modified
+ Product productToUpdate = await asyncSession.LoadAsync("products/111");
+ productToUpdate.Name = "Updated Name";
+
+ // Document 'products/999' is modified externally by another session
+ using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
+ {
+ Product otherProduct = await otherAsyncSession.LoadAsync("products/999");
+ otherProduct.Name = "Other Name";
+ await otherAsyncSession.SaveChangesAsync();
+ }
+
+ // Attempting to save will throw a 'ConcurrencyException'
+ // because even though 'products/999' was Not modified in this session,
+ // it was modified by another session since it was loaded.
+ await asyncSession.SaveChangesAsync(); // Throws 'ConcurrencyException'
+}
+```
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IDocumentSession session = store.OpenSession())
+{
+ // Load documents, the session will track them
+
+ // 'WritesAndReads' also checks unmodified tracked documents
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads;
+ // 'products/999' is tracked, will Not be modified
+ Product readOnlyProduct = session.Load("products/999");
+
+ // 'products/111' is tracked, will be modified
+ Product productToUpdate = session.Load("products/111");
+ productToUpdate.Name = "Updated Name";
+ // Document 'products/999' is modified externally by another session
+ using (IDocumentSession otherSession = store.OpenSession())
+ {
+ Product otherProduct = otherSession.Load("products/999");
+ otherProduct.Name = "Other Name";
+ otherSession.SaveChanges();
+ }
+
+ // Attempting to save will throw a 'ConcurrencyException'
+ // because even though 'products/999' was Not modified in this session,
+ // it was modified by another session since it was loaded.
+ session.SaveChanges(); // Throws 'ConcurrencyException'
+}
+```
+
+
+```csharp
+// Configure optimistic concurrency mode for this session only
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Load documents, the session will track them
+
+ // 'WritesAndReads' also checks unmodified tracked documents
+ asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.WritesAndReads;
-## Disable for specific document (when enabled on session)
+ // 'products/999' is tracked, will Not be modified
+ Product readOnlyProduct = await asyncSession.LoadAsync("products/999");
+
+ // 'products/111' is tracked, will be modified
+ Product productToUpdate = await asyncSession.LoadAsync("products/111");
+ productToUpdate.Name = "Updated Name";
+
+ // Document 'products/999' is modified externally by another session
+ using (IAsyncDocumentSession otherAsyncSession = store.OpenAsyncSession())
+ {
+ Product otherProduct = await otherAsyncSession.LoadAsync("products/999");
+ otherProduct.Name = "Other Name";
+ await otherAsyncSession.SaveChangesAsync();
+ }
+
+ // Attempting to save will throw a 'ConcurrencyException'
+ // because even though 'products/999' was Not modified in this session,
+ // it was modified by another session since it was loaded.
+ await asyncSession.SaveChangesAsync();
+}
+```
+
+
+
+
+
+
-* Optimistic concurrency can be _disabled when **storing** a specific document,
+* Optimistic concurrency can be _disabled_ when **storing** a specific document,
even when it is _enabled_ for an entire session (or globally).
* This is done by passing `null` as a change vector value to the [Store](../../../../client-api/session/storing-entities.mdx) method.
-
-
-{`using (IDocumentSession session = store.OpenSession())
-\{
- // Store document 'products/999'
- session.Store(new Product \{ Name = "Some Name" \}, id: "products/999");
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
+{
+ // Store new document 'products/999'
+ session.Store(new Product { Name = "Some Name" }, id: "products/999");
session.SaveChanges();
-\}
+}
using (IDocumentSession session = store.OpenSession())
-\{
+{
// Enable optimistic concurrency for the session
- session.Advanced.UseOptimisticConcurrency = true;
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
// Store the same document
- // Pass 'null' as the changeVector to turn OFF optimistic concurrency for this document
- session.Store(new Product \{ Name = "Some Other Name" \}, changeVector: null, id: "products/999");
+ // Pass 'null' as the change vector to turn OFF optimistic concurrency for this document
+ session.Store(
+ new Product { Name = "Some Other Name" },
+ changeVector: null,
+ id: "products/999"
+ );
// This will NOT throw a ConcurrencyException, and the document will be saved
session.SaveChanges();
-\}
-`}
-
+}
+```
+
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Store new document 'products/999'
+ await asyncSession.StoreAsync(new Product { Name = "Some Name" }, id: "products/999");
+ await asyncSession.SaveChangesAsync();
+}
+
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Enable optimistic concurrency for the session
+ asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
+ // Store the same document
+ // Pass 'null' as the change vector to turn OFF optimistic concurrency for this document
+ await asyncSession.StoreAsync(
+ new Product { Name = "Some Other Name" },
+ changeVector: null,
+ id: "products/999"
+ );
+
+ // This will NOT throw a ConcurrencyException, and the document will be saved
+ await asyncSession.SaveChangesAsync();
+}
+```
+
+
+
-## Enable for specific document (when disabled on session)
+
* Optimistic concurrency can be _enabled_ when **storing** a specific document,
even when it is _disabled_ for an entire session (or globally).
@@ -154,35 +592,233 @@ using (IDocumentSession session = store.OpenSession())
Setting the change vector to an empty string will cause RavenDB to ensure that this document is a new one and doesn't already exist.
A `ConcurrencyException` will be thrown if the document already exists.
-* If you do not provide a change vector or if the change vector is `null`, optimistic concurrency will be disabled.
-
-* Setting optimistic concurrency for a specific document overrides the `UseOptimisticConcurrency` property from the `Advanced` session operations.
+* If you do not provide a change vector or if the change vector is `null`, optimistic concurrency will be _disabled_.
-
-
-{`using (IDocumentSession session = store.OpenSession())
-\{
- // Store document 'products/999'
- session.Store(new Product \{ Name = "Some Name" \}, id: "products/999");
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
+{
+ // Store new document 'products/999'
+ session.Store(new Product { Name = "Some Name" }, id: "products/999");
session.SaveChanges();
-\}
+}
using (IDocumentSession session = store.OpenSession())
-\{
+{
// Disable optimistic concurrency for the session
- session.Advanced.UseOptimisticConcurrency = false; // This is also the default value
+ // This is also the default value
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.None;
// Store the same document
- // Pass 'string.Empty' as the changeVector to turn ON optimistic concurrency for this document
- session.Store(new Product \{ Name = "Some Other Name" \}, changeVector: string.Empty, id: "products/999");
+ // Pass 'string.Empty' as the change vector
+ // to turn ON optimistic concurrency for this document
+ session.Store(
+ new Product { Name = "Some Other Name" },
+ changeVector: string.Empty,
+ id: "products/999");
// This will throw a ConcurrencyException, and the document will NOT be saved
session.SaveChanges();
-\}
-`}
-
+}
+```
+
+```csharp
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Store new document 'products/999'
+ await asyncSession.StoreAsync(new Product { Name = "Some Name" }, id: "products/999");
+ await asyncSession.SaveChangesAsync();
+}
+
+using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
+{
+ // Disable optimistic concurrency for the session
+ // This is also the default value
+ asyncSession.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.None;
+ // Store the same document
+ // Pass 'string.Empty' as the change vector
+ // to turn ON optimistic concurrency for this document
+ await asyncSession.StoreAsync(
+ new Product { Name = "Some Other Name" },
+ changeVector: string.Empty,
+ id: "products/999");
+
+ // This will throw a ConcurrencyException, and the document will NOT be saved
+ await asyncSession.SaveChangesAsync();
+}
+```
+
+
+
+
+
+* [Cannot combine _Writes_ and _WritesAndReads_ with _NoTracking_](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#cannot-combinewritesandwritesandreadswithnotracking).
+* [Cannot combine _Writes_ and _WritesAndReads_ with _TransactionMode.ClusterWide_](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#cannot-combineewritesandwritesandreadswithtransactionmodeclusterwide).
+* [_UseOptimisticConcurrency_ is deprecated](../../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx#useoptimisticconcurrencyis-deprecated).
+* _WritesAndReads_ mode is not supported with [Sharding](../../../../sharding/overview.mdx).
+
+---
+
+
+### Cannot combine _Writes_ and _WritesAndReads_ with _NoTracking_
+
+* If you [Disable tracking](../../../../client-api/session/configuration/how-to-disable-tracking.mdx),
+ the session does Not track documents or retain their change vectors.
+ As a result, _Writes_ and _WritesAndReads_ modes cannot be used.
+
+* Setting `NoTracking` to `true` while using `Writes` or `WritesAndReads` mode will throw an `InvalidOperationException`.
+ The exception is thrown by whichever property is set second.
+
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ NoTracking = true,
+
+ // Setting this will throw an 'InvalidOperationException'
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
+}))
+{
+}
+```
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes,
+
+ // Setting this will throw an 'InvalidOperationException'
+ NoTracking = true
+}))
+{
+}
+```
+
+
+
+
+
+
+
+### Cannot combinee _Writes_ and _WritesAndReads_ with _TransactionMode.ClusterWide_
+
+* [Cluster-wide sessions](../../../../client-api/session/cluster-transaction/overview.mdx) cannot apply _Writes_ or _WritesAndReads_ modes.
+
+* Setting `Writes` or `WritesAndReads` mode on a cluster-wide session will throw an `InvalidOperationException`.
+ The exception is thrown by whichever property is set second.
+
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ TransactionMode = TransactionMode.ClusterWide,
+
+ // Setting this will throw an 'InvalidOperationException'
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes
+}))
+{
+}
+```
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession(new SessionOptions
+{
+ OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes,
+
+ // Setting this will throw an 'InvalidOperationException'
+ TransactionMode = TransactionMode.ClusterWide
+}))
+{
+}
+```
+
+
+
+
+
+
+
+### _UseOptimisticConcurrency_ is deprecated
+
+* The `UseOptimisticConcurrency` property is deprecated and will be removed in a future major version.
+ Use `OptimisticConcurrencyMode` instead.
+
+* `OptimisticConcurrencyMode` and the deprecated `UseOptimisticConcurrency` property cannot be used together on the same session.
+ Setting one after the other will throw an `InvalidOperationException`.
+
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
+{
+ session.Advanced.UseOptimisticConcurrency = true;
+
+ // Setting this will throw an 'InvalidOperationException'
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
+}
+```
+
+
+```csharp
+using (IDocumentSession session = store.OpenSession())
+{
+ session.Advanced.OptimisticConcurrencyMode = OptimisticConcurrencyMode.Writes;
+
+ // Setting this will throw an 'InvalidOperationException'
+ session.Advanced.UseOptimisticConcurrency = true;
+}
+```
+
+
+
+
+
+
+
+**Note**:
+In all cases above, the exception is also thrown if the conflicting condition is inherited from the [store conventions](../../../../client-api/configuration/conventions.mdx)
+rather than set explicitly in the session.
+
+
+
+
+
+
+
+```csharp
+// SessionOptions
+public OptimisticConcurrencyMode? OptimisticConcurrencyMode { get; set; }
+
+// session.Advanced
+public OptimisticConcurrencyMode OptimisticConcurrencyMode { get; set; }
+
+// Deprecated - use OptimisticConcurrencyMode instead
+[Obsolete]
+public bool UseOptimisticConcurrency { get; set; }
+
+// OptimisticConcurrencyMode enum
+public enum OptimisticConcurrencyMode
+{
+ // Default - no concurrency checks are performed (Last Write Wins)
+ None,
+
+ // Concurrency checks are performed only for modified or deleted entities
+ Writes,
+
+ // Concurrency checks are performed for ALL tracked entities,
+ // both modified and unmodified
+ WritesAndReads
+}
+```
+
+
\ No newline at end of file
diff --git a/docs/client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx b/docs/client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx
index 8140293de2..ad42e9414f 100644
--- a/docs/client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx
+++ b/docs/client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx
@@ -1,7 +1,7 @@
---
-title: "How to Enable Optimistic Concurrency"
-sidebar_label: How to Enable Optimistic Concurrency
-description: "Enable optimistic concurrency in RavenDB sessions to prevent overwriting changes made by other clients using change vector checks."
+title: "Configure Optimistic Concurrency"
+sidebar_label: "Configure Optimistic Concurrency"
+description: "Configure optimistic concurrency in RavenDB sessions to prevent overwriting changes made by other clients using change vector checks."
sidebar_position: 4
supported_languages: ["csharp", "java", "python", "php", "nodejs"]
see_also:
@@ -24,7 +24,6 @@ import HowToEnableOptimisticConcurrencyPython from './content/_how-to-enable-opt
import HowToEnableOptimisticConcurrencyPhp from './content/_how-to-enable-optimistic-concurrency-php.mdx';
import HowToEnableOptimisticConcurrencyNodejs from './content/_how-to-enable-optimistic-concurrency-nodejs.mdx';
-
diff --git a/docs/client-api/session/content/_opening-a-session-csharp.mdx b/docs/client-api/session/content/_opening-a-session-csharp.mdx
index 7bd2c8bf84..166f5dc1a5 100644
--- a/docs/client-api/session/content/_opening-a-session-csharp.mdx
+++ b/docs/client-api/session/content/_opening-a-session-csharp.mdx
@@ -64,27 +64,25 @@ IAsyncDocumentSession OpenAsyncSession(SessionOptions options);
|----------------------------------------------|-------------------------------|
| `IDocumentSession` / `IAsyncDocumentSession` | Instance of a Session object |
-
-
## Session options
* The `SessionOptions` object contains various options to configure the Session's behavior.
-| Option | Type | Description | Default Value |
-|---------------------------------------------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|
-| **Database** | `string` | The Session will operate on this database,
overriding the Default Database. | `null` - the Session operates on the Default Database |
-| **NoTracking** | `bool` | `true` - The Session tracks changes made to all entities it loaded, stored, or queried for.
`false` - Tracking will be turned off.
Learn more in [Disable tracking](../../../client-api/session/configuration/how-to-disable-tracking.mdx) | `false` |
-| **NoCaching** | `bool` | `true` - Server responses will Not be cached.
`false` - The Session caches the server responses.
Learn more in [Disable caching](../../../client-api/session/configuration/how-to-disable-caching.mdx) | `false` |
-| **RequestExecutor** | `RequestExecutor` | ( _Advanced option_ )
The request executor the Session should use. | `null` - the default request executor is used |
-| **TransactionMode** | `TransactionMode` | Specify the Session's transaction mode
`SingleNode` / `ClusterWide`
Learn more in [Cluster-wide vs. Single-node](../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) | `SingleNode` |
+| Option | Type | Description |
+|----------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Database** | `string` | The Session will operate on this database,
overriding the Default Database.
Default: `null` - The Session operates on the Default Database. |
+| **NoTracking** | `bool` | `true` - The Session tracks changes made to all entities it loaded, stored, or queried for.
`false` (Default) - Tracking will be turned off.
Learn more in [Disable tracking](../../../client-api/session/configuration/how-to-disable-tracking.mdx) |
+| **NoCaching** | `bool` | `true` - Server responses will Not be cached.
`false` (Default) - The Session caches the server responses.
Learn more in [Disable caching](../../../client-api/session/configuration/how-to-disable-caching.mdx) |
+| **RequestExecutor** | `RequestExecutor` | ( _Advanced option_ )
The request executor the Session should use. | `null` - the default request executor is used. |
+| **TransactionMode** | `TransactionMode` | Specify the Session's transaction mode:
`SingleNode` (Default) / `ClusterWide`
Learn more in [Cluster-wide vs. Single-node](../../../client-api/session/cluster-transaction/overview.mdx#cluster-wide-transaction-vs-single-node-transaction) |
+| **OptimisticConcurrencyMode** | `OptimisticConcurrencyMode?` | Configure the optimistic concurrency mode for the session.
When `null`, the session inherits the value from the [store conventions](../../../client-api/configuration/conventions.mdx#optimisticconcurrencymode).
Learn more in [Configure optimistic concurrency](../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx). |
* Experts Only:
-| Option | Type | Description | Default Value |
-|--------------------------------------------------------------|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
-| **DisableAtomicDocumentWrites-
InClusterWideTransaction** | `bool?` | ( _Experts only_ )
`true` - Disable Atomic-Guards in cluster-wide sessions.
`false` - Automatic atomic writes in cluster-wide sessions are enabled.
Learn more in [Atomic-Guards](../../../compare-exchange/atomic-guards.mdx) | `false` |
-
-
+| Option | Type | Description |
+|--------------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **DisableAtomicDocumentWrites-
InClusterWideTransaction** | `bool?` | `true` - Disable Atomic-Guards in cluster-wide sessions.
`false` (Default) - Automatic atomic writes in cluster-wide sessions are enabled.
Learn more in [Atomic-Guards](../../../compare-exchange/atomic-guards.mdx) |
+| **ShardedBatchBehavior** | `ShardedBatchBehavior?` | Define the consistency level for persisting changes in a sharded database. |
## Open session example
diff --git a/docs/client-api/session/content/_what-is-a-session-and-how-does-it-work-csharp.mdx b/docs/client-api/session/content/_what-is-a-session-and-how-does-it-work-csharp.mdx
index 7743579e3b..e22b784a5d 100644
--- a/docs/client-api/session/content/_what-is-a-session-and-how-does-it-work-csharp.mdx
+++ b/docs/client-api/session/content/_what-is-a-session-and-how-does-it-work-csharp.mdx
@@ -2,13 +2,15 @@ import Admonition from '@theme/Admonition';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
+import Panel from "@site/src/components/Panel";
+import ContentFrame from "@site/src/components/ContentFrame";
* The **Session**, which is obtained from the [Document Store](../../../client-api/what-is-a-document-store.mdx),
is the primary interface your application will interact with.
-* In this page:
+* In this article:
* [Session overview](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#session-overview)
* [Unit of work pattern](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#unit-of-work-pattern)
* [Tracking changes](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#tracking-changes)
@@ -17,13 +19,14 @@ import CodeBlock from '@theme/CodeBlock';
* [Identity map pattern](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#identity-map-pattern)
* [Batching & Transactions](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#batching--transactions)
* [Concurrency control](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#concurrency-control)
- * [Reducing server calls (best practices) for:](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#reducing-server-calls-(best-practices)-for:)
+ * [Reducing server calls (best practices)](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#reducing-server-calls-best-practices)
* [The N+1 problem](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#the-select-n1-problem)
* [Large query results](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#large-query-results)
* [Retrieving results on demand (Lazy)](../../../client-api/session/what-is-a-session-and-how-does-it-work.mdx#retrieving-results-on-demand-lazy)
-## Session overview
+
+
* **What is the session**:
@@ -59,11 +62,11 @@ import CodeBlock from '@theme/CodeBlock';
It is _not_ an Object–relational mapping (ORM) tool. Although if you're familiar with NHibernate of Entity Framework ORMs you'll recognize that
the session is equivalent of NHibernate's session and Entity Framework's DataContext which implement UoW pattern as well.
-
+
-## Unit of work pattern
+
-#### Tracking changes
+### Tracking changes
* Using the Session, perform needed operations on your documents.
e.g. create a new document, modify an existing document, query for documents, etc.
@@ -81,7 +84,9 @@ import CodeBlock from '@theme/CodeBlock';
* [Clear session](../../../client-api/session/how-to/clear-a-session.mdx)
* [Evict a single entity](../../../client-api/session/how-to/evict-entity-from-a-session.mdx)
-#### Create document example
+---
+
+### Create document example
* The Client API, and the Session in particular, is designed to be as straightforward as possible.
Open the session, do some operations, and apply the changes to the RavenDB server.
@@ -89,8 +94,8 @@ import CodeBlock from '@theme/CodeBlock';
-
-{`// Obtain a Session from your Document Store
+```csharp
+// Obtain a Session from your Document Store
using (IDocumentSession session = store.OpenSession())
{
// Create a new entity
@@ -105,12 +110,11 @@ using (IDocumentSession session = store.OpenSession())
// At this point the entity is persisted to the database as a new document.
// Since no database was specified when opening the Session, the Default Database is used.
}
-`}
-
+```
-
-{`// Obtain a Session from your Document Store
+```csharp
+// Obtain a Session from your Document Store
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Create a new entity
@@ -125,18 +129,20 @@ using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
// At this point the entity is persisted to the database as a new document.
// Since no database was specified when opening the Session, the Default Database is used.
}
-`}
-
+```
-#### Modify document example
+---
+
+### Modify document example
+
* The following example modifies the content of an existing document.
-
-{`// Open a session
+```csharp
+// Open a session
using (IDocumentSession session = store.OpenSession())
{
// Load an existing document to the Session using its ID
@@ -149,12 +155,11 @@ using (IDocumentSession session = store.OpenSession())
session.SaveChanges();
// At this point, the change made is persisted to the existing document in the database
}
-`}
-
+```
-
-{`// Open a Session
+```csharp
+// Open a Session
using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
{
// Load an existing document to the Session using its ID
@@ -167,14 +172,13 @@ using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
asyncSession.SaveChangesAsync();
// At this point, the change made is persisted to the existing document in the database
}
-`}
-
+```
-
+
-## Identity map pattern
+
* The session implements the [Identity Map Pattern](https://martinfowler.com/eaaCatalog/identityMap.html).
* The first `Load()` call goes to the server and fetches the document from the database.
@@ -184,8 +188,8 @@ using (IAsyncDocumentSession asyncSession = store.OpenAsyncSession())
-
-{`// A document is fetched from the server
+```csharp
+// A document is fetched from the server
Company entity1 = session.Load(companyId);
// Loading the same document will now retrieve its entity from the Session's map
@@ -193,12 +197,11 @@ Company entity2 = session.Load(companyId);
// This command will Not throw an exception
Assert.Same(entity1, entity2);
-`}
-
+```
-
-{`// A document is fetched from the server
+```csharp
+// A document is fetched from the server
Company entity1 = await asyncSession.LoadAsync(companyId);
// Loading the same document will now retrieve its entity from the Session's map
@@ -206,8 +209,7 @@ Company entity2 = await asyncSession.LoadAsync(companyId);
// This command will Not throw an exception
Assert.Same(entity1, entity2);
-`}
-
+```
@@ -215,9 +217,9 @@ Assert.Same(entity1, entity2);
To override this behavior and force `Load()` to fetch the latest changes from the server see:
[Refresh an entity](../../../client-api/session/how-to/refresh-entity.mdx).
+
-
-## Batching & Transactions
+
@@ -229,6 +231,7 @@ Assert.Same(entity1, entity2);
These changes are then combined into a single batch that is sent to the server in a **single remote call** and executed as a single ACID transaction.
+
#### Transactions
@@ -258,6 +261,7 @@ Assert.Same(entity1, entity2);
This can be done either with the usage of a [JavaScript patch](../../../client-api/operations/patching/single-document.mdx) syntax or [JSON Patch](../../../client-api/operations/patching/json-patch-syntax.mdx) syntax.
+
#### Transaction mode
@@ -274,64 +278,35 @@ Assert.Same(entity1, entity2);
-For a detailed description of transactions in RavenDB please refer to the [Transaction support in RavenDB](../../../client-api/faq/transaction-support.mdx) article.
+
+For a detailed description of transactions in RavenDB please refer to the [Transaction support in RavenDB](../../../client-api/faq/transaction-support.mdx) article.
+
+
+
-## Concurrency control
-
-The typical usage model of the session is:
-
- * Load documents
- * Modify the documents
- * Save changes
-
-For example, a real case scenario would be:
-
- * Load an entity from a database.
- * Display an Edit form on the screen.
- * Update the entity after the user completes editing.
-
-When using the session, the interaction with the database is divided into two parts - the load part and save changes part.
-Each of these parts is executed separately, via its own HTTP request.
-Consequently, data that was loaded and edited could potentially be changed by another user in the meantime.
-To address this, the session API offers the concurrency control feature.
-
-#### Default strategy on single node
-
-* By default, concurrency checks are turned off.
- This means that with the default configuration of the session, concurrent changes to the same document will use the Last Write Wins strategy.
-
-* The second write of an updated document will override the previous version, causing potential data loss.
- This behavior should be considered when using the session with single node transaction mode.
-
-#### Optimistic concurrency on single node
-
-* The modification or editing stage can extend over a considerable time period or may occur offline.
- To prevent conflicting writes, where a document is modified while it is being edited by another user or client,
- the session can be configured to employ [optimistic concurrency](../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx).
-
-* Once optimistic concurrency is enabled, the session performs version tracking to ensure that any document modified within the session has not been altered in the database since it was loaded into the session.
- The version is tracked using a [change vector](../../../server/clustering/replication/change-vector.mdx).
-
-* When `SaveChanges()` is called, the session additionally transmits the version of the modified documents to the database, allowing it to verify if any changes have occurred in the meantime.
- If modifications are detected, the transaction will be aborted with a `ConcurrencyException`,
- providing the caller with an opportunity to retry or handle the error as needed.
-
-#### Concurrency control in cluster-wide transactions
-
-* In a cluster-wide transaction scenario, RavenDB server tracks a cluster-wide version for each modified document, updating it through the Raft protocol.
- This means that when using a session with the cluster-wide transaction mode, a `ConcurrencyException` will be triggered upon calling `SaveChanges()`
- if another user has modified a document and saved it in a separate cluster-wide transaction in the meantime.
+A session may load documents, modify them in memory, and save the changes back to the database.
+Since loading/querying and saving occur in **separate requests**, other sessions or clients may modify the same documents during this time -
+between when your session loads the document and when `SaveChanges()` is called.
+
+By default, sessions do not detect these conflicts and use a "Last Write Wins" strategy,
+where the most recent `SaveChanges()` call overwrites any prior changes made by other sessions.
-* More information about cluster transactions can be found in [Cluster Transaction - Overview](../../../client-api/session/cluster-transaction/overview.mdx).
+If your application requires detecting and preventing conflicting updates rather than silently overwriting them,
+you can enable **optimistic concurrency**, which ensures documents have not been modified by other sessions since they were loaded into your session.
+See [Enable optimistic concurrency](../../../client-api/session/configuration/how-to-enable-optimistic-concurrency.mdx) for detailed behavior and configuration options.
+
-## Reducing server calls (best practices) for:
+
+
+Reducing server calls (best practices) for:
#### The select N+1 problem
+
* The Select N+1 problem is common
with all ORMs and ORM-like APIs.
It results in an excessive number of remote calls to the server, which makes a query very expensive.
@@ -339,6 +314,7 @@ To address this, the session API offers the concurrency control feature.
See: [Document relationships](../../../client-api/how-to/handle-document-relationships.mdx)
#### Large query results
+
* When query results are large and you don't want the overhead of keeping all results in memory,
then you can [Stream query results](../../../querying/stream-query-results.mdx).
A single server call is executed and the client can handle the results one by one.
@@ -346,9 +322,8 @@ To address this, the session API offers the concurrency control feature.
however, multiple server calls are generated - one per page retrieved.
#### Retrieving results on demand (Lazy)
+
* Query calls to the server can be delayed and executed on-demand as needed using `Lazily()`
* See [Perform queries lazily](../../../client-api/session/querying/how-to-perform-queries-lazily.mdx)
-
-
-
+
\ No newline at end of file