Transactions Migration Guide
For those transitioning from using the Couchbase Transactions library for .NET.
Couchbase transactions for .NET were originally introduced as a library separate from the main Couchbase .NET SDK.
We subsequently chose to integrate transactions directly into the SDKs with the release of .NET SDK 3.2.8, to make it easier for users to get started.
This document details the small changes that existing users of the legacy transactions library need to make, to migrate to the SDK-integrated version.
The legacy transactions library will continue to be supported with bugfixes for some time, but new transaction features will only be added to the SDK and it is recommended that all users migrate.
Accessing transactions
This is now done via a Cluster object.
There is no longer any need to create a Transactions object.
var transactions = Transactions.Create(cluster);
await transactions.RunAsync(async (ctx) => {
// Your transaction logic.
});
var bucket = await cluster.BucketAsync("default").ConfigureAwait(false);
var collection = await bucket.ScopeAsync("inventory").CollectionAsync("airport").ConfigureAwait(false);
// Use the cluster's Transactions object
var transactions = cluster.Transactions;
Configuration
Configuration used to be performed when creating the Transactions object, and is now performed when creating the Cluster object.
var transactions = Transactions.Create(cluster,
TransactionsConfigBuilder.Create()
.DurabilityLevel(DurabilityLevel.PersistToMajority)
.Build());
var transactionsConfig = TransactionsConfigBuilder.Create()
.DurabilityLevel(DurabilityLevel.PersistToMajority)
.Build();
var options = new ClusterOptions()
{
UserName = "Administrator",
Password = "Administrator",
TransactionsConfig = transactionsConfig,
};
var cluster = await Cluster.ConnectAsync("couchbase://your-ip", options).ConfigureAwait(false);
A few details of configuration change:
-
TransactionsConfigBuilder.MetadataCollectiontakes aKeyspaceinstead of anICouchbaseCollection(since a collection cannot be created at this point). -
KeyValueTimeouthas been removed fromTransactionsConfigandTransactionOptions. -
TransactionDurabilityLevelhas been dropped in favour of using the SDK’sDurabilityLevel. -
TransactionOptionsnow allows aMetadataCollectionparameter.
Cleanup configuration
Cleanup configuration options have been encapsulated into their own class:
var transactions = Transactions.Create(cluster,
TransactionsConfigBuilder.Create()
.CleanupConfig(TransactionCleanupConfigBuilder.Create()
.CleanupClientAttempts(false)
.CleanupLostAttempts(false)
.CleanupWindow(TimeSpan.FromSeconds(30))
.Build())
.Build());
var cleanupConfig = TransactionCleanupConfigBuilder.Create()
.CleanupClientAttempts(false)
.CleanupLostAttempts(false)
.CleanupWindow(TimeSpan.FromSeconds(120))
.Build();
var transactionsConfig =
TransactionsConfigBuilder.Create()
.CleanupConfig(cleanupConfig).Build();
var options = new ClusterOptions()
{
UserName = "Administrator",
Password = "Administrator",
TransactionsConfig = transactionsConfig,
};
var cluster = await Cluster.ConnectAsync("couchbase://your-ip", options).ConfigureAwait(false);
Lambda
ctx.CommitAsync() has been removed from the public API, as it is redundant: commit automatically happens when the lambda successfully reaches the end.
ctx.RollbackAsync() has been removed from the public API, as it too is redundant: the application can throw any exception from the lambda to trigger a rollback.
Package changes
All classes have changed namespaces to be compatible with the .NET SDK conventions.
The simplest way to convert many of the classes to their new locations is to search for using Couchbase.Transactions; and replace it with using Couchbase.Client.Transactions;.
Some additional manual conversion after this may be required.
Single query transactions
These are now integrated with the transactions object on the cluster.
SingleQueryTransactionResult sqr = await transactions.QueryAsync<dynamic>("INSERT...");
var bulkLoadStatement = "<a bulk-loading N1QL statement>";
try
{
SingleQueryTransactionResult<object> result = await transactions.QueryAsync<object>(bulkLoadStatement);
IQueryResult<object> queryResult = result.QueryResult;
}
catch (TransactionCommitAmbiguousException e)
{
Console.Error.WriteLine("Transaction possibly committed");
foreach (var log in e.Result.Logs)
{
Console.Error.WriteLine(log);
}
}
catch (TransactionFailedException e)
{
Console.Error.WriteLine("Transaction did not reach commit point");
foreach (var log in e.Result.Logs)
{
Console.Error.WriteLine(log);
}
}
Or with configuration:
SingleQueryTransactionResult sqr = await transactions.QueryAsync<dynamic>("INSERT...",
SingleQueryTransactionConfigBuilder.Create()
// configure timeouts, durability etc
);
// with the Builder pattern.
await transactions.QueryAsync<object>(bulkLoadStatement, SingleQueryTransactionConfigBuilder.Create()
// Single query transactions will often want to increase the default timeout
.ExpirationTime(TimeSpan.FromSeconds(360)));
// using the lambda style
await transactions.QueryAsync<object>(bulkLoadStatement, config => config.ExpirationTime(TimeSpan.FromSeconds(360)));
Cleanup
This doesn’t impact the API, but it is useful to know that lost cleanup has changed.
Previously, lost cleanup would look for expired transactions on the default collections of all buckets in the cluster. Unless a metadata collection was specified, in which case only that collection would be cleaned up.
Now, cleanup is dynamic. As transactions are run, the collection where metadata is created for that transaction, is added to what we call the 'cleanup set'. This is just the set of collections where cleanup, for this application, is looking for expired transactions.
The intent has always been that users without complex requirements should never need to think about or configure transaction cleanup, and this new dynamic cleanup allows us to be closer still to that goal.
Logging
Failure logging remains largely the same, but note that TransactionFailedException contains a Result property which exposes the transaction Logs.
try
{
var result = await transactions.RunAsync(async ctx => {
// ... transactional code here ...
});
}
catch (TransactionFailedException err)
{
// ... log the error as you normally would
// then include the logs
foreach (var logLine in err.Result.Logs)
{
Console.Error.WriteLine(logLine);
}
}
Further Reading
-
There’s plenty of explanation about how Transactions work in Couchbase in our Transactions documentation.