diff --git a/modules/devguide/examples/dotnet/Cas.cs b/modules/devguide/examples/dotnet/Cas.cs index 13404399..c9200d92 100644 --- a/modules/devguide/examples/dotnet/Cas.cs +++ b/modules/devguide/examples/dotnet/Cas.cs @@ -28,7 +28,7 @@ public override async Task ExecuteAsync() await Task.WhenAll(tasksWithoutCas).ConfigureAwait(false); // Check if the actual result is 1000 as expected - var result = await Bucket.DefaultCollection().GetAsync(key).ConfigureAwait(false); + using var result = await Bucket.DefaultCollection().GetAsync(key).ConfigureAwait(false); Console.WriteLine("Expected number = 1000, actual number = " + result.ContentAs().Number); // Set the initial number value back to 0 @@ -39,7 +39,7 @@ public override async Task ExecuteAsync() await Task.WhenAll(tasksWithCas).ConfigureAwait(false); // Check if the actual result is 1000 as expected - var result2 = await Bucket.DefaultCollection().GetAsync(key).ConfigureAwait(false); + using var result2 = await Bucket.DefaultCollection().GetAsync(key).ConfigureAwait(false); Console.WriteLine("Expected number = 1000, actual number = " + result2.ContentAs().Number); } diff --git a/modules/devguide/examples/dotnet/Cloud/Cloud.cs b/modules/devguide/examples/dotnet/Cloud/Cloud.cs index a51a7117..716dbe7f 100644 --- a/modules/devguide/examples/dotnet/Cloud/Cloud.cs +++ b/modules/devguide/examples/dotnet/Cloud/Cloud.cs @@ -1,10 +1,16 @@ using System; -// #tag::using[] using System.Threading.Tasks; -using Couchbase; + +namespace Couchbase.Net.DevGuide.Cloud; // #end::using[] -await new CloudExample().Main(); +public class Progam +{ + public static async Task Main(string[] args) + { + await new CloudExample().Main(); + } +} class CloudExample { @@ -44,7 +50,7 @@ public async Task Main() // #tag::upsert-get[] // Upsert Document var upsertResult = await collection.UpsertAsync("my-document-key", new { Name = "Ted", Age = 31 }); - var getResult = await collection.GetAsync("my-document-key"); + using var getResult = await collection.GetAsync("my-document-key"); Console.WriteLine(getResult.ContentAs()); // #end::upsert-get[] @@ -60,4 +66,4 @@ public async Task Main() } // end::n1ql-query[] } -} +} \ No newline at end of file diff --git a/modules/devguide/examples/dotnet/CloudConnect.cs b/modules/devguide/examples/dotnet/CloudConnect.cs index 50498784..07529a28 100644 --- a/modules/devguide/examples/dotnet/CloudConnect.cs +++ b/modules/devguide/examples/dotnet/CloudConnect.cs @@ -44,7 +44,7 @@ static async Task Main(string[] args) }); // Load the Document and print it - var getResult = await collection.GetAsync("king_arthur"); + using var getResult = await collection.GetAsync("king_arthur"); Console.WriteLine(getResult.ContentAs()); // Perform a N1QL Query diff --git a/modules/devguide/examples/dotnet/CouchbaseCloud.cs b/modules/devguide/examples/dotnet/CouchbaseCloud.cs index db397e0d..6331dd02 100644 --- a/modules/devguide/examples/dotnet/CouchbaseCloud.cs +++ b/modules/devguide/examples/dotnet/CouchbaseCloud.cs @@ -35,7 +35,7 @@ private static void Main2(string[] args) private async Task RunTest() { - var getResult = await collection.GetAsync("airline_5209").ConfigureAwait(false); + using var getResult = await collection.GetAsync("airline_5209").ConfigureAwait(false); var docContent = getResult.ContentAs(); Console.WriteLine($"The airline is {docContent.name}."); diff --git a/modules/devguide/examples/dotnet/DevGuide.csproj b/modules/devguide/examples/dotnet/DevGuide.csproj index 89976e56..f91bde9b 100644 --- a/modules/devguide/examples/dotnet/DevGuide.csproj +++ b/modules/devguide/examples/dotnet/DevGuide.csproj @@ -1,7 +1,7 @@ Exe - netcoreapp5.0 + .net6 Couchbase.Net.DevGuide Couchbase.Net.DevGuide.Program @@ -11,7 +11,7 @@ - + diff --git a/modules/devguide/examples/dotnet/Retrieve.cs b/modules/devguide/examples/dotnet/Retrieve.cs index dba0161f..c580f23d 100644 --- a/modules/devguide/examples/dotnet/Retrieve.cs +++ b/modules/devguide/examples/dotnet/Retrieve.cs @@ -25,7 +25,7 @@ public override async Task ExecuteAsync() // Get non-existent document. // Note that it's enough to check the Status property, // We're only checking all three to show they exist. - await collection.GetAsync(key).ConfigureAwait(false); + using var result = await collection.GetAsync(key).ConfigureAwait(false); } catch (DocumentNotFoundException) { @@ -36,14 +36,14 @@ public override async Task ExecuteAsync() await collection.UpsertAsync(key, "Hello Couchbase!").ConfigureAwait(false); // Get a string value - var nonDocResult = await collection.GetAsync(key).ConfigureAwait(false); + using var nonDocResult = await collection.GetAsync(key).ConfigureAwait(false); Console.WriteLine("Found: " + nonDocResult.ContentAs()); // Prepare a JSON document value await collection.UpsertAsync(key, data).ConfigureAwait(false); // Get a JSON document string value - var docResult = await collection.GetAsync(key).ConfigureAwait(false); + using var docResult = await collection.GetAsync(key).ConfigureAwait(false); Console.WriteLine("Found: " + docResult.ContentAs()); } diff --git a/modules/devguide/examples/dotnet/SyncExample.cs b/modules/devguide/examples/dotnet/SyncExample.cs index a2419e8c..2d765aea 100644 --- a/modules/devguide/examples/dotnet/SyncExample.cs +++ b/modules/devguide/examples/dotnet/SyncExample.cs @@ -32,7 +32,7 @@ public async Task PrintDocumentAsync(string id) Console.WriteLine("Before awaiting GetDocumentAsync on thread {0}.", Thread.CurrentThread.ManagedThreadId); - var doc = await Bucket.DefaultCollection().GetAsync(id).ConfigureAwait(false); + using var doc = await Bucket.DefaultCollection().GetAsync(id).ConfigureAwait(false); Console.WriteLine("After awaiting GetDocumentAsync on thread {0}.", Thread.CurrentThread.ManagedThreadId); diff --git a/modules/devguide/examples/dotnet/Update.cs b/modules/devguide/examples/dotnet/Update.cs index 5029316a..52ec84e2 100644 --- a/modules/devguide/examples/dotnet/Update.cs +++ b/modules/devguide/examples/dotnet/Update.cs @@ -46,7 +46,7 @@ public override async Task ExecuteAsync() // Check that the data was updated - var newDocument = await collection.GetAsync(key).ConfigureAwait(false); + using var newDocument = await collection.GetAsync(key).ConfigureAwait(false); Console.WriteLine("Got: " + newDocument.ContentAs()); } diff --git a/modules/howtos/examples/EncryptingUsingSdk.csx b/modules/howtos/examples/EncryptingUsingSdk.csx index 2da7611c..c4df328b 100644 --- a/modules/howtos/examples/EncryptingUsingSdk.csx +++ b/modules/howtos/examples/EncryptingUsingSdk.csx @@ -69,7 +69,7 @@ public class EncryptingUsingSdk .ConfigureAwait(false); // tag::encrypting_using_sdk_4[] - var getResult1 = await collection.GetAsync(id, options => options.Transcoder(encryptedTranscoder)) + using var getResult1 = await collection.GetAsync(id, options => options.Transcoder(encryptedTranscoder)) .ConfigureAwait(false); var encrypted = getResult1.ContentAs(); @@ -77,7 +77,7 @@ public class EncryptingUsingSdk // end::encrypting_using_sdk_4[] // tag::encrypting_using_sdk_5[] - var getResult2 = await collection.GetAsync(id, options => options.Transcoder(encryptedTranscoder)) + using var getResult2 = await collection.GetAsync(id, options => options.Transcoder(encryptedTranscoder)) .ConfigureAwait(false); var readItBack = getResult2.ContentAs(); diff --git a/modules/howtos/examples/ErrorHandling.csx b/modules/howtos/examples/ErrorHandling.csx index 45586042..efa2e639 100644 --- a/modules/howtos/examples/ErrorHandling.csx +++ b/modules/howtos/examples/ErrorHandling.csx @@ -43,11 +43,11 @@ public class ErrorHandling try { // tag::getfetch[] // This will raise a `CouchbaseException` and propagate it - var result = await collection.GetAsync("my-document-id"); + using var result1 = await collection.GetAsync("my-document-id"); // Rethrow with a custom exception type try { - await collection.GetAsync("my-document-id"); + using var result2 = await collection.GetAsync("my-document-id"); } catch (CouchbaseException ex) { throw new Exception("Couchbase lookup failed", ex); } @@ -61,7 +61,7 @@ public class ErrorHandling Console.WriteLine("[getcatch]"); // tag::getcatch[] try { - await collection.GetAsync("my-document-id"); + using var result = await collection.GetAsync("my-document-id"); } catch (DocumentNotFoundException) { await collection.InsertAsync("my-document-id", new {my ="value"}); } catch (CouchbaseException ex) { @@ -100,7 +100,7 @@ public class ErrorHandling Console.WriteLine("[customgrequest]"); IRetryStrategy myCustomStrategy = null; // tag::customreq[] - await collection.GetAsync("docid", new GetOptions().RetryStrategy(myCustomStrategy)); + using var result = await collection.GetAsync("docid", new GetOptions().RetryStrategy(myCustomStrategy)); // end::customreq[] } } diff --git a/modules/howtos/examples/KvOperations.csx b/modules/howtos/examples/KvOperations.csx index ad6cd55f..0a699749 100644 --- a/modules/howtos/examples/KvOperations.csx +++ b/modules/howtos/examples/KvOperations.csx @@ -109,7 +109,7 @@ public class KvOperations // tag::get[] var previousResult = await collection.UpsertAsync("string-key", "string value"); - var result = await collection.GetAsync("string-key"); + using var result = await collection.GetAsync("string-key"); var content = result.ContentAs(); // end::get[] } diff --git a/modules/howtos/examples/SubDocument.csx b/modules/howtos/examples/SubDocument.csx index 8fb15fcd..b3d04d84 100644 --- a/modules/howtos/examples/SubDocument.csx +++ b/modules/howtos/examples/SubDocument.csx @@ -40,7 +40,7 @@ await _collection.UpsertAsync("customer123", document); { Console.WriteLine("get:"); // #tag::get[] - var result = await _collection.LookupInAsync("customer123", specs => + using var result = await _collection.LookupInAsync("customer123", specs => specs.Get("addresses.delivery.country") ); @@ -219,7 +219,7 @@ await Concurrent(); async Task CasAsync() { Console.WriteLine("cas:"); // #tag::cas[] - var player = await _collection.GetAsync("player432"); + using var player = await _collection.GetAsync("player432"); ulong decrement = 150; await _collection.MutateInAsync("player432", specs => specs.Decrement("gold", decrement), diff --git a/modules/howtos/examples/Transcoding.csx b/modules/howtos/examples/Transcoding.csx index 9d70b745..b992878e 100644 --- a/modules/howtos/examples/Transcoding.csx +++ b/modules/howtos/examples/Transcoding.csx @@ -40,7 +40,7 @@ public class Transcoding // #end::raw-json-encode[] // #tag::raw-json-decode[] - var rawJsonDecodeResult = + using var rawJsonDecodeResult = await collection.GetAsync("john-smith", options => options.Transcoder(new RawJsonTranscoder())); var returnedJson = rawJsonDecodeResult.ContentAs(); @@ -58,7 +58,7 @@ public class Transcoding }); - var stringResult = await collection.GetAsync(docId, options => options.Transcoder(new RawStringTranscoder())); + using var stringResult = await collection.GetAsync(docId, options => options.Transcoder(new RawStringTranscoder())); var returnedString = stringResult.ContentAs(); // #end::string[] @@ -70,7 +70,7 @@ public class Transcoding await collection.UpsertAsync(docId, strBytes, options => options.Transcoder(new RawBinaryTranscoder())); - var binaryResult = await collection.GetAsync(docId, options => options.Transcoder(new RawBinaryTranscoder())); + using var binaryResult = await collection.GetAsync(docId, options => options.Transcoder(new RawBinaryTranscoder())); var returnedBinary = binaryResult.ContentAs(); // #end::binary[] @@ -84,7 +84,7 @@ public class Transcoding await collection.UpsertAsync(docId, bytes, options => options.Transcoder(new RawBinaryTranscoder())); - var binaryMemoryResult = await collection.GetAsync(docId, options => options.Transcoder(new RawBinaryTranscoder())); + using var binaryMemoryResult = await collection.GetAsync(docId, options => options.Transcoder(new RawBinaryTranscoder())); // Be sure to dispose of the IMemoryOwner when done, typically via a using statement using var binary = binaryMemoryResult.ContentAs>(); @@ -106,7 +106,7 @@ public class Transcoding // #tag::custom-decode[] - var customDecodeResult = await collection.GetAsync("john-smith", options => options.Transcoder(transcoder)); + using var customDecodeResult = await collection.GetAsync("john-smith", options => options.Transcoder(transcoder)); var returnedUser = customDecodeResult.ContentAs(); // #end::custom-decode[] @@ -137,7 +137,7 @@ public class Transcoding // #tag::msgpack-decode[] - var msgpackResult = await collection.GetAsync("john-smith", options => options.Transcoder(msgpackTranscoder)); + using var msgpackResult = await collection.GetAsync("john-smith", options => options.Transcoder(msgpackTranscoder)); var msgpackReturnedUser = msgpackResult.ContentAs(); // #end::msgpack-decode[] } diff --git a/modules/howtos/examples/UserManagementExample.csx b/modules/howtos/examples/UserManagementExample.csx index 704b0793..8179e8c0 100644 --- a/modules/howtos/examples/UserManagementExample.csx +++ b/modules/howtos/examples/UserManagementExample.csx @@ -99,7 +99,7 @@ public class UserManagementExample Console.WriteLine("Primary index already exists!"); } - var returnedAirline10doc = await collection.GetAsync("airline_10"); + using var returnedAirline10doc = await collection.GetAsync("airline_10"); await collection.UpsertAsync( "airline_11", new { @@ -112,7 +112,7 @@ public class UserManagementExample } ); - var returnedAirline11Doc = await collection.GetAsync("airline_11"); + using var returnedAirline11Doc = await collection.GetAsync("airline_11"); Console.WriteLine($"get -> { returnedAirline11Doc.ContentAs() }"); var result = await userCluster.QueryAsync( diff --git a/modules/howtos/pages/managing-connections.adoc b/modules/howtos/pages/managing-connections.adoc index 2e5322db..0bddb807 100644 --- a/modules/howtos/pages/managing-connections.adoc +++ b/modules/howtos/pages/managing-connections.adoc @@ -226,7 +226,6 @@ NOTE: Capella's root certificate is *not* signed by a well known CA (Certificate However, as the certificate is bundled with the SDK when using .NET 6.0 or later, it is trusted by default. .NET Framework clients will have to add it to the Windows certificate store. - === Couchbase Server As of SDK 3.4, if you connect to a Couchbase Server cluster with a root certificate issued by a trusted CA (Certificate Authority), you no longer need to configure this in the `ClusterOptions`. diff --git a/modules/project-docs/pages/compatibility.adoc b/modules/project-docs/pages/compatibility.adoc index 03be0c32..a41c5463 100644 --- a/modules/project-docs/pages/compatibility.adoc +++ b/modules/project-docs/pages/compatibility.adoc @@ -153,17 +153,12 @@ include::hello-world:partial$supported.adoc[] | *✔* | *✔* -|=== - - - -//// -// To add after 3.4.14 release: | .NET 8.0 (Long-Term Support) | *✖* | *✔* From 3.4.14 | *✔* -//// +|=== + // TODO - any outstanding improvements touched upon in https://github.com/couchbase/docs-sdk-dotnet/pull/310 diff --git a/modules/project-docs/pages/performance.adoc b/modules/project-docs/pages/performance.adoc index bf6fb6a7..eabe671e 100644 --- a/modules/project-docs/pages/performance.adoc +++ b/modules/project-docs/pages/performance.adoc @@ -11,6 +11,18 @@ Learn how to keep get the best performance from your application. == Coding Best Practices +=== Always call Dispose on GetAsync, LookupInAsync, etc. + +`GetAsync` and other retrieval operations return a class the implements the `IDisposable` interface. +It is important that on these result types, `Dispose()` is called so that the buffer that has between "rented" that handles the response is returned back to its pool. + +[source,csharp] +---- +using var result = await collection.GetAsync("key1"); +---- + +This applies to `GetAsync`, `LookupInAsync`, `GetAndLockAsync`, `GetAndTouchAsync`, `GetAnyReplicaAsync`, and `GetAllReplicasAsync`. + === Avoid Running Tasks on the "Captured Context" When a Task is waited, the continuation may occur on the same thread which created the Task. @@ -37,7 +49,8 @@ Then a collection is opened using a non-async overload. Finally, we perform a CRUD operation, and again we disable the context. This pattern should be followed throughout your Couchbase application. -NOTE: In modern ASP.NET (5+) the core lacks a default synchronization. With this core, usage of `Task.ConfigureAwait(false)` is debatable as it adds a small amount of overhead. +NOTE: In modern ASP.NET (5+), the core lacks a default synchronization. +With this core, usage of `Task.ConfigureAwait(false)` is debatable as it adds a small amount of overhead. === Avoid Synchronously Awaiting Foreach Loops @@ -75,7 +88,9 @@ You are better off batching and awaiting `Task.WhenAll` so that blocking does no === Do Use Parallel.ForEachAsync -In the latest .NET Framework Versions (6+), there is a https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-6.0[special method] that allows you to control the amount of parallelism for scheduled asynchronous work: +In the latest .NET Framework Versions (6+), there is a +https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-6.0[special method] +that allows you to control the amount of parallelism for scheduled asynchronous work: [source,csharp] ---- @@ -89,7 +104,8 @@ await Parallel.ForEachAsync(keys, async (key, cancellationToken) => { #endif ---- -You can tune this by changing the batch size and the https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions?view=net-6.0[parallel options]; there is no universal best practice here as it depends on a number of factors. +You can tune this by changing the batch size and the https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions?view=net-6.0[parallel options]; +there is no universal best practice here as it depends on a number of factors. === Use Batching @@ -121,7 +137,8 @@ This is a trival example as partitioning keys may be very domain specific, but i === Avoid Sync over Async Antipattern -The Couchbase .NET SDK was designed for asynchronous the entire way down; however, at the application layer you can still block and wait for each operation. +The Couchbase .NET SDK was designed for asynchronous the entire way down; +however, at the application layer you can still block and wait for each operation. Doing this is an antipattern and may cause deadlocks, and most definitly will degrade performance. Here is an example of _sync_ over _async_: @@ -169,8 +186,11 @@ When we open the Cluster and Bucket objects, we create long-lived socket connect There is cost associated with creating these connections, so we want them to be reused over and over. If we're opening and closing these objects, we're creating and the tearing down these connections -- which causes latency, and may cause memory pressure. -The Couchbase SDK has a complementary https://docs.couchbase.com/dotnet-sdk/current/howtos/managing-connections.html#connection-di[Dependency Injection (DI)] library that makes this trival to manage. -Additionally, there are other ways of doing this manually in `Start.cs`, or for legacy applications, using `Application_Start` and `Application_End` handlers in the `Global.asax` file. +The Couchbase SDK has a complementary +https://docs.couchbase.com/dotnet-sdk/current/howtos/managing-connections.html#connection-di[Dependency Injection (DI)] +library that makes this trival to manage. +Additionally, there are other ways of doing this manually in `Start.cs`, or for legacy applications, +using `Application_Start` and `Application_End` handlers in the `Global.asax` file. We strongly recommend users of the SDK use the DI library approach as its the simplest and easiest to debug. @@ -197,7 +217,8 @@ The `SingleConnectionPool` is a very simple pool that contains exactly one conne It's useful for debugging connection related problems as it is has very few features and allows you to quickly isolate problems. It may also be suitable for some applications or micro-services that need to constrain the number of active connections. However, in general, we suggest using the `ChannelConnectionPool`. -As mentioned above, setting both `ClusterOptions.NumKvConnections` and `ClusterOptions.MaxKvConnections` to a number less than or equal to `1` will cause the `SingleConnectionPool` to be used. +As mentioned above, +setting both `ClusterOptions.NumKvConnections` and `ClusterOptions.MaxKvConnections` to a number less than or equal to `1` will cause the `SingleConnectionPool` to be used. The `DataFlowConnectionPool` is a legacy pool and should not be used in new applications. @@ -219,7 +240,8 @@ Defaults to 1MiB. ==== MaximumRetainedOperationBuilders Maximum number of buffers used for building key-value operations to be sent to the server which will be retained for reuse. -If your application has a very high degree of parallelism (for example, a very large number of data nodes), increasing this number may improve performance at the cost of RAM utilization. +If your application has a very high degree of parallelism (for example, a very large number of data nodes), +increasing this number may improve performance at the cost of RAM utilization. Defaults to the 4 times the number of logical CPUs. === Operation Tracing and Metrics @@ -227,7 +249,8 @@ Defaults to the 4 times the number of logical CPUs. The SDK by default enables operation tracing and metrics tracking. It is used to generate threshold and orphan response reports which are written to the log file. While these are useful tool for debugging, they do come at a cost of increased memory and CPU use. -Operation tracing and metrics can be disabled by setting the `ClusterOptions.TracingOptions.Enabled` flag to `false` and/or by setting the `ClusterOptions.LoggingMeterOptions.Enabled` to `false`. +Operation tracing and metrics can be disabled by setting the `ClusterOptions.TracingOptions.Enabled` flag to `false` +and/or by setting the `ClusterOptions.LoggingMeterOptions.Enabled` to `false`. Note, by doing so you will lose the ability to use these useful debugging tools. === Logging diff --git a/modules/project-docs/pages/sdk-release-notes.adoc b/modules/project-docs/pages/sdk-release-notes.adoc index f4d3f8c6..f71071af 100644 --- a/modules/project-docs/pages/sdk-release-notes.adoc +++ b/modules/project-docs/pages/sdk-release-notes.adoc @@ -25,6 +25,71 @@ All patch releases for each dot minor release should be API compatible, and safe any changes to expected behavior are noted in the release notes that follow. +[#version-3-4-15] +=== Version 3.4.15 (09 Feb 2024) + +Version 3.4.15 is the sixteenth release of the 3.4 series. + +https://packages.couchbase.com/clients/net/3.4/Couchbase-Net-Client-3.4.15.zip[Download] | +https://docs.couchbase.com/sdk-api/couchbase-net-client-3.4.15[API Reference] | +https://www.nuget.org/packages/CouchbaseNetClient/3.4.15[Nuget] + +==== Fixed Issues + +* https://issues.couchbase.com/browse/NCBC-3599[NCBC-3599]: +Fixed SDK bugs related to Nullability of Increment, Decrement, and related options. +* https://issues.couchbase.com/browse/NCBC-3565[NCBC-3565]: +Added error handling for "index does not exist" query error. + +==== New Features and Behavioral Changes + +* https://issues.couchbase.com/browse/NCBC-3579[NCBC-3579]: +Support `DocumentNotLocked` exception when `collection.Unlock()` is called on a document that is not locked. +* https://issues.couchbase.com/browse/NCBC-3606[NCBC-3606]: +Added SDK Support for Scoped Search Indexes. +* https://issues.couchbase.com/browse/NCBC-3596[NCBC-3596]: +Support added for `maxTTL` value of -1, for collection "no expiry". + + +[#version-3-4-14] +=== Version 3.4.14 (18 Jan 2024) + +Version 3.4.14 is the fifteenth release of the 3.4 series. + +https://packages.couchbase.com/clients/net/3.4/Couchbase-Net-Client-3.4.14.zip[Download] | +https://docs.couchbase.com/sdk-api/couchbase-net-client-3.4.14[API Reference] | +https://www.nuget.org/packages/CouchbaseNetClient/3.4.14[Nuget] + +==== Fixed Issues + +* https://issues.couchbase.com/browse/NCBC-3434[NCBC-3434]: +A regression introduced in a recent release prevented `WaitUntilReady` from pinging nodes -- +this has been fixed, and `WaitUntilReady` now correctly detects state. +// The rest of these issues need editing to make them consistent, and helpful to customers - but I'm out of time. :-/ +* https://issues.couchbase.com/browse/NCBC-3503[NCBC-3503]: +There was a possibility of `ClusterVersionProvider.GetVersionAsync` failing, if nodes have no `ManagementUri`, owing to randomized node order. +This has been fixed, and `GetRandomManagementUri()` should never now throw `NullReferenceException`. + +==== New Features and Behavioral Changes + +* https://issues.couchbase.com/browse/NCBC-3530[NCBC-3530]: +Made the `ConnectionString` class now accept `couchbase2` schema. +* https://issues.couchbase.com/browse/NCBC-3532[NCBC-3532]: +Added `AsReadOnly` record to Index APIs options. +* https://issues.couchbase.com/browse/NCBC-3538[NCBC-3538]: +Improved recovery time in Config Push to 300ms. +* https://issues.couchbase.com/browse/NCBC-3541[NCBC-3541]: +Added `AsReadOnly` record to Bucket Management APIs options. +* https://issues.couchbase.com/browse/NCBC-3551[NCBC-3551]: +Added `AsReadOnly` record to Collection Management APIs options. +* https://issues.couchbase.com/browse/NCBC-3568[NCBC-3568]: +Added `AsReadOnly` Record to GetAllScopesOptions. +* https://issues.couchbase.com/browse/NCBC-3516[NCBC-3516]: +Made fallback usage of `DefaultSerializer` trimmable. +* https://issues.couchbase.com/browse/NCBC-3518[NCBC-3518]: +Removed internal transcoder dependencies on `DefaultSerializer`. + + [#version-3-4-13] === Version 3.4.13 (09 Nov 2023) @@ -431,7 +496,7 @@ https://www.nuget.org/packages/CouchbaseNetClient/3.4.4[Nuget] * https://issues.couchbase.com/browse/NCBC-3340[NCBC-3340]: When an op timed out, the socket connection was closed and then recreated. With a large number of unexpected timeouts, many sockets could be left in `TIME_WAIT`. -Making `ChannelConnectionProcessor` reuse connections after timeout shoeld reduce the number of file descripters and local ports left open. +Making `ChannelConnectionProcessor` reuse connections after timeout should reduce the number of file descripters and local ports left open. * https://issues.couchbase.com/browse/NCBC-3356[NCBC-3356]: Cluster level `query_context`, which is not supported by Server versions earlier than 7.0, has been removed for these versions. * https://issues.couchbase.com/browse/NCBC-3343[NCBC-3343]: