diff --git a/.editorconfig b/.editorconfig index 1ab98bee463..482cea58e85 100644 --- a/.editorconfig +++ b/.editorconfig @@ -47,10 +47,10 @@ dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion -# Types: use keywords instead of BCL types, and permit var only when the type is clear -csharp_style_var_for_built_in_types = false:suggestion -csharp_style_var_when_type_is_apparent = false:none -csharp_style_var_elsewhere = false:suggestion +# Types: use keywords instead of BCL types, and prefer var instead of the explicit type +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion diff --git a/src/MongoDB.Driver/AggregateHelper.cs b/src/MongoDB.Driver/AggregateHelper.cs new file mode 100644 index 00000000000..c61628b5330 --- /dev/null +++ b/src/MongoDB.Driver/AggregateHelper.cs @@ -0,0 +1,96 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Linq; +using MongoDB.Bson; + +namespace MongoDB.Driver +{ + internal static class AggregateHelper + { + public static RenderedPipelineDefinition RenderAggregatePipeline(PipelineDefinition pipeline, RenderArgs renderArgs, out bool isAggregateToCollection) + { + var renderedPipeline = pipeline.Render(renderArgs); + + var lastStage = renderedPipeline.Documents.LastOrDefault(); + var lastStageName = lastStage?.GetElement(0).Name; + isAggregateToCollection = lastStageName == "$out" || lastStageName == "$merge"; + + return renderedPipeline; + } + + public static CollectionNamespace GetOutCollection(BsonDocument outStage, DatabaseNamespace defaultDatabaseNamespace) + { + var stageName = outStage.GetElement(0).Name; + switch (stageName) + { + case "$out": + { + var outValue = outStage[0]; + DatabaseNamespace outputDatabaseNamespace; + string outputCollectionName; + if (outValue.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = outValue.AsString; + } + else + { + outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); + outputCollectionName = outValue["coll"].AsString; + } + return new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); + } + case "$merge": + { + var mergeArguments = outStage[0]; + DatabaseNamespace outputDatabaseNamespace; + string outputCollectionName; + if (mergeArguments.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = mergeArguments.AsString; + } + else + { + var into = mergeArguments.AsBsonDocument["into"]; + if (into.IsString) + { + outputDatabaseNamespace = defaultDatabaseNamespace; + outputCollectionName = into.AsString; + } + else + { + if (into.AsBsonDocument.Contains("db")) + { + outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); + } + else + { + outputDatabaseNamespace = defaultDatabaseNamespace; + } + outputCollectionName = into["coll"].AsString; + } + } + return new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); + } + default: + throw new ArgumentException($"Unexpected stage name: {stageName}."); + } + } + } +} + diff --git a/src/MongoDB.Driver/IOperationExecutor.cs b/src/MongoDB.Driver/IOperationExecutor.cs index 1af86d6ff62..b935561469f 100644 --- a/src/MongoDB.Driver/IOperationExecutor.cs +++ b/src/MongoDB.Driver/IOperationExecutor.cs @@ -16,20 +16,40 @@ using System; using System.Threading; using System.Threading.Tasks; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Operations; namespace MongoDB.Driver { - internal interface IOperationExecutor + internal interface IOperationExecutor : IDisposable { - TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken); - Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken); + TResult ExecuteReadOperation( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken); - TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken); - Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken); + Task ExecuteReadOperationAsync( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken); - IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken); - Task StartImplicitSessionAsync(CancellationToken cancellationToken); + TResult ExecuteWriteOperation( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken); + + Task ExecuteWriteOperationAsync( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken); + + IClientSessionHandle StartImplicitSession(); } } diff --git a/src/MongoDB.Driver/MongoClient.cs b/src/MongoDB.Driver/MongoClient.cs index 0fa1b4eb8d2..55d91940952 100644 --- a/src/MongoDB.Driver/MongoClient.cs +++ b/src/MongoDB.Driver/MongoClient.cs @@ -23,7 +23,6 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Logging; using MongoDB.Driver.Core.Misc; @@ -42,9 +41,12 @@ public sealed class MongoClient : IMongoClient private readonly IClusterInternal _cluster; #pragma warning restore CA2213 // Disposable fields should be disposed private readonly IAutoEncryptionLibMongoCryptController _libMongoCryptController; + private readonly Func _operationExecutorFactory; private readonly IOperationExecutor _operationExecutor; private readonly MongoClientSettings _settings; private readonly ILogger _logger; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; // constructors /// @@ -60,23 +62,9 @@ public MongoClient() /// /// The settings. public MongoClient(MongoClientSettings settings) + : this(settings, client => new OperationExecutor(client)) { - _settings = Ensure.IsNotNull(settings, nameof(settings)).FrozenCopy(); - _logger = _settings.LoggingSettings?.CreateLogger(); - _cluster = _settings.ClusterSource.Get(_settings.ToClusterKey()); - _operationExecutor = new OperationExecutor(this); - if (settings.AutoEncryptionOptions != null) - { - _libMongoCryptController = - MongoClientSettings.Extensions.AutoEncryptionProvider.CreateAutoCryptClientController(this, settings.AutoEncryptionOptions); - - _settings.LoggingSettings?.CreateLogger()?.LogTrace( - StructuredLogTemplateProviders.TopologyId_Message_SharedLibraryVersion, - _cluster.ClusterId, - "CryptClient created. Configured shared library version: ", - _libMongoCryptController.CryptSharedLibraryVersion() ?? "None"); - } } /// @@ -97,10 +85,27 @@ public MongoClient(string connectionString) { } - internal MongoClient(IOperationExecutor operationExecutor, MongoClientSettings settings) - : this(settings) + internal MongoClient(MongoClientSettings settings, Func operationExecutorFactory) { - _operationExecutor = operationExecutor; + _settings = Ensure.IsNotNull(settings, nameof(settings)).FrozenCopy(); + _operationExecutorFactory = Ensure.IsNotNull(operationExecutorFactory, nameof(operationExecutorFactory)); + _logger = _settings.LoggingSettings?.CreateLogger(); + _cluster = _settings.ClusterSource.Get(_settings.ToClusterKey()); + _operationExecutor = _operationExecutorFactory(this); + _readOperationOptions = new(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new(); + + if (settings.AutoEncryptionOptions != null) + { + _libMongoCryptController = + MongoClientSettings.Extensions.AutoEncryptionProvider.CreateAutoCryptClientController(this, settings.AutoEncryptionOptions); + + _settings.LoggingSettings?.CreateLogger()?.LogTrace( + StructuredLogTemplateProviders.TopologyId_Message_SharedLibraryVersion, + _cluster.ClusterId, + "CryptClient created. Configured shared library version: ", + _libMongoCryptController.CryptSharedLibraryVersion() ?? "None"); + } } // public properties @@ -112,13 +117,11 @@ internal MongoClient(IOperationExecutor operationExecutor, MongoClientSettings s // internal properties internal IAutoEncryptionLibMongoCryptController LibMongoCryptController => ThrowIfDisposed(_libMongoCryptController); - internal IOperationExecutor OperationExecutor => ThrowIfDisposed(_operationExecutor); // internal methods internal void ConfigureAutoEncryptionMessageEncoderSettings(MessageEncoderSettings messageEncoderSettings) { ThrowIfDisposed(); - var autoEncryptionOptions = _settings.AutoEncryptionOptions; if (autoEncryptionOptions != null) { @@ -133,32 +136,36 @@ internal void ConfigureAutoEncryptionMessageEncoderSettings(MessageEncoderSettin // public methods /// public ClientBulkWriteResult BulkWrite(IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - => UsingImplicitSession(session => BulkWrite(session, models, options, cancellationToken), cancellationToken); + { + ThrowIfDisposed(); + using var session = _operationExecutor.StartImplicitSession(); + return BulkWrite(session, models, options, cancellationToken); + } /// public ClientBulkWriteResult BulkWrite(IClientSessionHandle session, IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(session, nameof(session)); + ThrowIfDisposed(); var operation = CreateClientBulkWriteOperation(models, options); return ExecuteWriteOperation(session, operation, cancellationToken); } /// - public Task BulkWriteAsync(IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) - => UsingImplicitSession(session => BulkWriteAsync(session, models, options, cancellationToken), cancellationToken); - - /// - public Task BulkWriteAsync(IClientSessionHandle session, IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) + public async Task BulkWriteAsync(IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) { - var operation = CreateClientBulkWriteOperation(models, options); - return ExecuteWriteOperationAsync(session, operation, cancellationToken); + ThrowIfDisposed(); + using var session = _operationExecutor.StartImplicitSession(); + return await BulkWriteAsync(session, models, options, cancellationToken).ConfigureAwait(false); } /// - public void DropDatabase(string name, CancellationToken cancellationToken = default(CancellationToken)) + public Task BulkWriteAsync(IClientSessionHandle session, IReadOnlyList models, ClientBulkWriteOptions options = null, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(session, nameof(session)); ThrowIfDisposed(); - - UsingImplicitSession(session => DropDatabase(session, name, cancellationToken), cancellationToken); + var operation = CreateClientBulkWriteOperation(models, options); + return ExecuteWriteOperationAsync(session, operation, cancellationToken); } /// @@ -180,6 +187,7 @@ public void Dispose(bool disposing) { _logger?.LogDebug(_cluster.ClusterId, "MongoClient disposing"); + _operationExecutor.Dispose(); _settings.ClusterSource.Return(_cluster); _libMongoCryptController?.Dispose(); @@ -191,39 +199,37 @@ public void Dispose(bool disposing) } /// - public void DropDatabase(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) + public void DropDatabase(string name, CancellationToken cancellationToken = default) { - Ensure.IsNotNull(session, nameof(session)); ThrowIfDisposed(); - - var messageEncoderSettings = GetMessageEncoderSettings(); - var operation = new DropDatabaseOperation(new DatabaseNamespace(name), messageEncoderSettings) - { - WriteConcern = _settings.WriteConcern - }; - ExecuteWriteOperation(session, operation, cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + DropDatabase(session, name, cancellationToken); } /// - public Task DropDatabaseAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) + public void DropDatabase(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(session, nameof(session)); ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => DropDatabaseAsync(session, name, cancellationToken), cancellationToken); + var operation = CreateDropDatabaseOperation(name); + ExecuteWriteOperation(session, operation, cancellationToken); } /// - public Task DropDatabaseAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) + public async Task DropDatabaseAsync(string name, CancellationToken cancellationToken = default) { ThrowIfDisposed(); + using var session = _operationExecutor.StartImplicitSession(); + await DropDatabaseAsync(session, name, cancellationToken).ConfigureAwait(false); + } + /// + public Task DropDatabaseAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + { Ensure.IsNotNull(session, nameof(session)); - var messageEncoderSettings = GetMessageEncoderSettings(); - var operation = new DropDatabaseOperation(new DatabaseNamespace(name), messageEncoderSettings) - { - WriteConcern = _settings.WriteConcern - }; - return ExecuteWriteOperationAsync(session, operation, cancellationToken); + ThrowIfDisposed(); + var opertion = CreateDropDatabaseOperation(name); + return ExecuteWriteOperationAsync(session, opertion, cancellationToken); } /// @@ -241,205 +247,143 @@ public IMongoDatabase GetDatabase(string name, MongoDatabaseSettings settings = } /// - public IAsyncCursor ListDatabaseNames( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNames(options: null, cancellationToken); - } + public IAsyncCursor ListDatabaseNames(CancellationToken cancellationToken = default) + => ListDatabaseNames(options: null, cancellationToken); /// public IAsyncCursor ListDatabaseNames( ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabaseNames(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return ListDatabaseNames(session, options, cancellationToken); } /// public IAsyncCursor ListDatabaseNames( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNames(session, options: null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabaseNames(session, options: null, cancellationToken); /// public IAsyncCursor ListDatabaseNames( IClientSessionHandle session, ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); var databases = ListDatabases(session, listDatabasesOptions, cancellationToken); - return CreateDatabaseNamesCursor(databases); } /// - public Task> ListDatabaseNamesAsync( - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNamesAsync(options: null, cancellationToken); - } + public Task> ListDatabaseNamesAsync(CancellationToken cancellationToken = default) + => ListDatabaseNamesAsync(options: null, cancellationToken); /// - public Task> ListDatabaseNamesAsync( + public async Task> ListDatabaseNamesAsync( ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabaseNamesAsync(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await ListDatabaseNamesAsync(session, options, cancellationToken).ConfigureAwait(false); } /// public Task> ListDatabaseNamesAsync( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabaseNamesAsync(session, options: null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabaseNamesAsync(session, options: null, cancellationToken); /// public async Task> ListDatabaseNamesAsync( IClientSessionHandle session, ListDatabaseNamesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - var listDatabasesOptions = CreateListDatabasesOptionsFromListDatabaseNamesOptions(options); var databases = await ListDatabasesAsync(session, listDatabasesOptions, cancellationToken).ConfigureAwait(false); - return CreateDatabaseNamesCursor(databases); } /// - public IAsyncCursor ListDatabases( - CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor ListDatabases(CancellationToken cancellationToken) { ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabases(session, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return ListDatabases(session, cancellationToken); } /// public IAsyncCursor ListDatabases( ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSession(session => ListDatabases(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return ListDatabases(session, options, cancellationToken); } /// public IAsyncCursor ListDatabases( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabases(session, null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabases(session, null, cancellationToken); /// public IAsyncCursor ListDatabases( IClientSessionHandle session, ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - Ensure.IsNotNull(session, nameof(session)); - options = options ?? new ListDatabasesOptions(); - var messageEncoderSettings = GetMessageEncoderSettings(); - var translationOptions = _settings.TranslationOptions; - var operation = CreateListDatabaseOperation(options, messageEncoderSettings, translationOptions); + var operation = CreateListDatabasesOperation(options); return ExecuteReadOperation(session, operation, cancellationToken); } /// - public Task> ListDatabasesAsync( - CancellationToken cancellationToken = default(CancellationToken)) + public async Task> ListDatabasesAsync(CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabasesAsync(session, null, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await ListDatabasesAsync(session, cancellationToken).ConfigureAwait(false); } /// - public Task> ListDatabasesAsync( + public async Task> ListDatabasesAsync( ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => ListDatabasesAsync(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await ListDatabasesAsync(session, options, cancellationToken).ConfigureAwait(false); } /// public Task> ListDatabasesAsync( IClientSessionHandle session, - CancellationToken cancellationToken = default(CancellationToken)) - { - ThrowIfDisposed(); - - return ListDatabasesAsync(session, null, cancellationToken); - } + CancellationToken cancellationToken = default) + => ListDatabasesAsync(session, null, cancellationToken); /// public Task> ListDatabasesAsync( IClientSessionHandle session, ListDatabasesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); ThrowIfDisposed(); - - options = options ?? new ListDatabasesOptions(); - var messageEncoderSettings = GetMessageEncoderSettings(); - var translationOptions = _settings.TranslationOptions; - var operation = CreateListDatabaseOperation(options, messageEncoderSettings, translationOptions); + var operation = CreateListDatabasesOperation(options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - /// - /// Starts an implicit session. - /// - /// A session. - internal IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - - return StartImplicitSession(); - } - - /// - /// Starts an implicit session. - /// - /// A Task whose result is a session. - internal Task StartImplicitSessionAsync(CancellationToken cancellationToken) - { - ThrowIfDisposed(); - - return Task.FromResult(StartImplicitSession()); - } - /// - public IClientSessionHandle StartSession(ClientSessionOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IClientSessionHandle StartSession(ClientSessionOptions options = null, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -447,7 +391,7 @@ internal Task StartImplicitSessionAsync(CancellationToken } /// - public Task StartSessionAsync(ClientSessionOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task StartSessionAsync(ClientSessionOptions options = null, CancellationToken cancellationToken = default) { ThrowIfDisposed(); @@ -458,11 +402,11 @@ internal Task StartImplicitSessionAsync(CancellationToken public IChangeStreamCursor Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Watch(session, pipeline, options, cancellationToken); } /// @@ -470,26 +414,24 @@ public IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); ThrowIfDisposed(); - - var translationOptions = _settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperation(session, operation, cancellationToken); } /// - public Task> WatchAsync( + public async Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { ThrowIfDisposed(); - - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await WatchAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } /// @@ -497,15 +439,12 @@ public Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - ThrowIfDisposed(); - - var translationOptions = _settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } @@ -513,40 +452,37 @@ public Task> WatchAsync( public IMongoClient WithReadConcern(ReadConcern readConcern) { Ensure.IsNotNull(readConcern, nameof(readConcern)); - ThrowIfDisposed(); var newSettings = Settings.Clone(); newSettings.ReadConcern = readConcern; - return new MongoClient(_operationExecutor, newSettings); + return new MongoClient(newSettings, _operationExecutorFactory); } /// public IMongoClient WithReadPreference(ReadPreference readPreference) { Ensure.IsNotNull(readPreference, nameof(readPreference)); - ThrowIfDisposed(); var newSettings = Settings.Clone(); newSettings.ReadPreference = readPreference; - return new MongoClient(_operationExecutor, newSettings); + return new MongoClient(newSettings, _operationExecutorFactory); } /// public IMongoClient WithWriteConcern(WriteConcern writeConcern) { Ensure.IsNotNull(writeConcern, nameof(writeConcern)); - ThrowIfDisposed(); var newSettings = Settings.Clone(); newSettings.WriteConcern = writeConcern; - return new MongoClient(_operationExecutor, newSettings); + return new MongoClient(newSettings, _operationExecutorFactory); } // private methods - private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList models, ClientBulkWriteOptions options = null) + private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList models, ClientBulkWriteOptions options) { if (_settings.AutoEncryptionOptions != null) { @@ -578,17 +514,22 @@ private ClientBulkWriteOperation CreateClientBulkWriteOperation(IReadOnlyList CreateDatabaseNamesCursor(IAsyncCursor cursor) - { - return new BatchTransformingAsyncCursor( + => new BatchTransformingAsyncCursor( cursor, databases => databases.Select(database => database["name"].AsString)); - } - private ListDatabasesOperation CreateListDatabaseOperation( - ListDatabasesOptions options, - MessageEncoderSettings messageEncoderSettings, - ExpressionTranslationOptions translationOptions) + private DropDatabaseOperation CreateDropDatabaseOperation(string name) + => new(new DatabaseNamespace(name), GetMessageEncoderSettings()) + { + WriteConcern = _settings.WriteConcern + }; + + private ListDatabasesOperation CreateListDatabasesOperation(ListDatabasesOptions options) { + options ??= new ListDatabasesOptions(); + var messageEncoderSettings = GetMessageEncoderSettings(); + var translationOptions = _settings.TranslationOptions; + return new ListDatabasesOperation(messageEncoderSettings) { AuthorizedDatabases = options.AuthorizedDatabases, @@ -612,69 +553,28 @@ private ListDatabasesOptions CreateListDatabasesOptionsFromListDatabaseNamesOpti return listDatabasesOptions; } - private IReadBindingHandle CreateReadBinding(IClientSessionHandle session) - { - var readPreference = _settings.ReadPreference; - if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) - { - throw new InvalidOperationException("Read preference in a transaction must be primary."); - } - - var binding = new ReadPreferenceBinding(_cluster, readPreference, session.WrappedCoreSession.Fork()); - return new ReadBindingHandle(binding); - } - - private IReadWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) - { - var binding = new WritableServerBinding(_cluster, session.WrappedCoreSession.Fork()); - return new ReadWriteBindingHandle(binding); - } - private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) - { - return ChangeStreamHelper.CreateChangeStreamOperation( + ChangeStreamOptions options) + => ChangeStreamHelper.CreateChangeStreamOperation( pipeline, options, _settings.ReadConcern, GetMessageEncoderSettings(), _settings.RetryReads, - translationOptions); - } + _settings.TranslationOptions); - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } + private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperation(session, operation, _readOperationOptions, false, cancellationToken); - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } + private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperationAsync(session, operation, _readOperationOptions, false, cancellationToken); - private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); - } - } + private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperation(session, operation, _writeOperationOptions, false, cancellationToken); - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } + private Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperationAsync(session, operation, _writeOperationOptions, false, cancellationToken); private MessageEncoderSettings GetMessageEncoderSettings() { @@ -696,13 +596,6 @@ private RenderArgs GetRenderArgs() return new RenderArgs(BsonDocumentSerializer.Instance, serializerRegistry, translationOptions: translationOptions); } - private IClientSessionHandle StartImplicitSession() - { - var options = new ClientSessionOptions { CausalConsistency = false, Snapshot = false }; - ICoreSessionHandle coreSession = _cluster.StartSession(options.ToCore(isImplicit: true)); - return new ClientSessionHandle(this, options, coreSession); - } - private IClientSessionHandle StartSession(ClientSessionOptions options) { if (options != null && options.Snapshot && options.CausalConsistency == true) @@ -718,37 +611,5 @@ private IClientSessionHandle StartSession(ClientSessionOptions options) private void ThrowIfDisposed() => ThrowIfDisposed(string.Empty); private T ThrowIfDisposed(T value) => _disposed ? throw new ObjectDisposedException(GetType().Name) : value; - - private void UsingImplicitSession(Action func, CancellationToken cancellationToken) - { - using (var session = StartImplicitSession(cancellationToken)) - { - func(session); - } - } - - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken) - { - using (var session = StartImplicitSession(cancellationToken)) - { - return func(session); - } - } - - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken) - { - using (var session = await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - await funcAsync(session).ConfigureAwait(false); - } - } - - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken) - { - using (var session = await StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - return await funcAsync(session).ConfigureAwait(false); - } - } } } diff --git a/src/MongoDB.Driver/MongoCollectionImpl.cs b/src/MongoDB.Driver/MongoCollectionImpl.cs index 51f03dc0a04..4b0b144e960 100644 --- a/src/MongoDB.Driver/MongoCollectionImpl.cs +++ b/src/MongoDB.Driver/MongoCollectionImpl.cs @@ -21,8 +21,6 @@ using MongoDB.Bson; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; -using MongoDB.Driver.Core; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; @@ -41,6 +39,8 @@ internal sealed class MongoCollectionImpl : MongoCollectionBase _documentSerializer; private readonly MongoCollectionSettings _settings; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; // constructors public MongoCollectionImpl(IMongoDatabase database, CollectionNamespace collectionNamespace, MongoCollectionSettings settings, IClusterInternal cluster, IOperationExecutor operationExecutor) @@ -58,6 +58,8 @@ private MongoCollectionImpl(IMongoDatabase database, CollectionNamespace collect _documentSerializer = Ensure.IsNotNull(documentSerializer, nameof(documentSerializer)); _messageEncoderSettings = GetMessageEncoderSettings(); + _readOperationOptions = new(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new(); } // properties @@ -94,36 +96,25 @@ public override MongoCollectionSettings Settings } // public methods - public override IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Aggregate(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Aggregate(session, pipeline, options, cancellationToken: cancellationToken); } - public override IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { @@ -132,36 +123,25 @@ public override MongoCollectionSettings Settings } } - public override Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await AggregateAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public override async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult>(deferredCursor).ConfigureAwait(false); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { @@ -170,23 +150,21 @@ public override MongoCollectionSettings Settings } } - public override void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => AggregateToCollection(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + AggregateToCollection(session, pipeline, options, cancellationToken); } - public override void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (renderedPipeline.Documents.Count == 0 || !isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollection requires that the last stage be $out or $merge."); } @@ -195,56 +173,46 @@ public override MongoCollectionSettings Settings ExecuteWriteOperation(session, aggregateOperation, cancellationToken); } - public override Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateToCollectionAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await AggregateToCollectionAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public override async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new AggregateOptions(); + options ??= new AggregateOptions(); var renderArgs = GetRenderArgs(options.TranslationOptions); - var renderedPipeline = pipeline.Render(renderArgs); - - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (renderedPipeline.Documents.Count == 0 || !isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollectionAsync requires that the last stage be $out or $merge."); } var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); + return ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken); } - public override BulkWriteResult BulkWrite(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override BulkWriteResult BulkWrite(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => BulkWrite(session, requests, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return BulkWrite(session, requests, options, cancellationToken); } - public override BulkWriteResult BulkWrite(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override BulkWriteResult BulkWrite(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull((object)requests, nameof(requests)); - + Ensure.IsNotNull(requests, nameof(requests)); var requestsArray = requests.ToArray(); if (requestsArray.Length == 0) { throw new ArgumentException("Must contain at least 1 request.", nameof(requests)); } - foreach (var request in requestsArray) - { - request.ThrowIfNotValid(); - } - - options = options ?? new BulkWriteOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateBulkWriteOperation(session, requestsArray, options, renderArgs); + var operation = CreateBulkWriteOperation(session, requestsArray, options); try { var result = ExecuteWriteOperation(session, operation, cancellationToken); @@ -256,31 +224,23 @@ public override MongoCollectionSettings Settings } } - public override Task> BulkWriteAsync(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> BulkWriteAsync(IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => BulkWriteAsync(session, requests, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await BulkWriteAsync(session, requests, options, cancellationToken).ConfigureAwait(false); } - public override async Task> BulkWriteAsync(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> BulkWriteAsync(IClientSessionHandle session, IEnumerable> requests, BulkWriteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - Ensure.IsNotNull((object)requests, nameof(requests)); - + Ensure.IsNotNull(requests, nameof(requests)); var requestsArray = requests.ToArray(); if (requestsArray.Length == 0) { throw new ArgumentException("Must contain at least 1 request.", nameof(requests)); } - foreach (var request in requestsArray) - { - request.ThrowIfNotValid(); - } - - options = options ?? new BulkWriteOptions(); - - var renderArgs = GetRenderArgs(); - var operation = CreateBulkWriteOperation(session, requestsArray, options, renderArgs); + var operation = CreateBulkWriteOperation(session, requestsArray, options); try { var result = await ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); @@ -293,314 +253,296 @@ public override MongoCollectionSettings Settings } [Obsolete("Use CountDocuments or EstimatedDocumentCount instead.")] - public override long Count(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override long Count(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Count(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Count(session, filter, options, cancellationToken); } [Obsolete("Use CountDocuments or EstimatedDocumentCount instead.")] - public override long Count(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override long Count(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateCountOperation(filter, options, renderArgs); + var operation = CreateCountOperation(filter, options); return ExecuteReadOperation(session, operation, cancellationToken); } [Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")] - public override Task CountAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task CountAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => CountAsync(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await CountAsync(session, filter, options, cancellationToken).ConfigureAwait(false); } [Obsolete("Use CountDocumentsAsync or EstimatedDocumentCountAsync instead.")] - public override Task CountAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task CountAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateCountOperation(filter, options, renderArgs); + var operation = CreateCountOperation(filter, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - public override long CountDocuments(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override long CountDocuments(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => CountDocuments(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return CountDocuments(session, filter, options, cancellationToken); } - public override long CountDocuments(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override long CountDocuments(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateCountDocumentsOperation(filter, options, renderArgs); + var operation = CreateCountDocumentsOperation(filter, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task CountDocumentsAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task CountDocumentsAsync(FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => CountDocumentsAsync(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await CountDocumentsAsync(session, filter, options, cancellationToken).ConfigureAwait(false); } - public override Task CountDocumentsAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task CountDocumentsAsync(IClientSessionHandle session, FilterDefinition filter, CountOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new CountOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateCountDocumentsOperation(filter, options, renderArgs); + var operation = CreateCountDocumentsOperation(filter, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - public override IAsyncCursor Distinct(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Distinct(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Distinct(session, field, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Distinct(session, field, filter, options, cancellationToken); } - public override IAsyncCursor Distinct(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor Distinct(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(field, nameof(field)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctOperation(field, filter, options, renderArgs); + var operation = CreateDistinctOperation(field, filter, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task> DistinctAsync(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> DistinctAsync(FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => DistinctAsync(session, field, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await DistinctAsync(session, field, filter, options, cancellationToken).ConfigureAwait(false); } - public override Task> DistinctAsync(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> DistinctAsync(IClientSessionHandle session, FieldDefinition field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(field, nameof(field)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctOperation(field, filter, options, renderArgs); + var operation = CreateDistinctOperation(field, filter, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - public override IAsyncCursor DistinctMany(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor DistinctMany(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => DistinctMany(session, field, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return DistinctMany(session, field, filter, options, cancellationToken); } - public override IAsyncCursor DistinctMany(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor DistinctMany(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(field, nameof(field)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctManyOperation(field, filter, options, renderArgs); + var operation = CreateDistinctManyOperation(field, filter, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task> DistinctManyAsync(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> DistinctManyAsync(FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => DistinctManyAsync(session, field, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await DistinctManyAsync(session, field, filter, options, cancellationToken).ConfigureAwait(false); } - public override Task> DistinctManyAsync(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> DistinctManyAsync(IClientSessionHandle session, FieldDefinition> field, FilterDefinition filter, DistinctOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(field, nameof(field)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new DistinctOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateDistinctManyOperation(field, filter, options, renderArgs); + var operation = CreateDistinctManyOperation(field, filter, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - public override long EstimatedDocumentCount(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override long EstimatedDocumentCount(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => - { - var operation = CreateEstimatedDocumentCountOperation(options); - return ExecuteReadOperation(session, operation, cancellationToken); - }); + using var session = _operationExecutor.StartImplicitSession(); + var operation = CreateEstimatedDocumentCountOperation(options); + return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task EstimatedDocumentCountAsync(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task EstimatedDocumentCountAsync(EstimatedDocumentCountOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => - { - var operation = CreateEstimatedDocumentCountOperation(options); - return ExecuteReadOperationAsync(session, operation, cancellationToken); - }); + using var session = _operationExecutor.StartImplicitSession(); + var operation = CreateEstimatedDocumentCountOperation(options); + return await ExecuteReadOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); } - public override IAsyncCursor FindSync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor FindSync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => FindSync(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return FindSync(session, filter, options, cancellationToken); } - public override IAsyncCursor FindSync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor FindSync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOptions(); - var renderArgs = GetRenderArgs(options.TranslationOptions); - var operation = CreateFindOperation(filter, options, renderArgs); + var operation = CreateFindOperation(filter, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task> FindAsync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> FindAsync(FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => FindAsync(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await FindAsync(session, filter, options, cancellationToken).ConfigureAwait(false); } - public override Task> FindAsync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> FindAsync(IClientSessionHandle session, FilterDefinition filter, FindOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOptions(); - var renderArgs = GetRenderArgs(options.TranslationOptions); - var operation = CreateFindOperation(filter, options, renderArgs); + var operation = CreateFindOperation(filter, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } - public override TProjection FindOneAndDelete(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndDelete(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => FindOneAndDelete(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return FindOneAndDelete(session, filter, options, cancellationToken); } - public override TProjection FindOneAndDelete(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndDelete(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOneAndDeleteOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndDeleteOperation(filter, options, renderArgs); + var operation = CreateFindOneAndDeleteOperation(filter, options); return ExecuteWriteOperation(session, operation, cancellationToken); } - public override Task FindOneAndDeleteAsync(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task FindOneAndDeleteAsync(FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => FindOneAndDeleteAsync(session, filter, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await FindOneAndDeleteAsync(session, filter, options, cancellationToken).ConfigureAwait(false); } - public override Task FindOneAndDeleteAsync(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task FindOneAndDeleteAsync(IClientSessionHandle session, FilterDefinition filter, FindOneAndDeleteOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); - options = options ?? new FindOneAndDeleteOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndDeleteOperation(filter, options, renderArgs); + var operation = CreateFindOneAndDeleteOperation(filter, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } - public override TProjection FindOneAndReplace(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndReplace(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => FindOneAndReplace(session, filter, replacement, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return FindOneAndReplace(session, filter, replacement, options, cancellationToken); } - public override TProjection FindOneAndReplace(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndReplace(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); var replacementObject = Ensure.IsNotNull((object)replacement, nameof(replacement)); // only box once if it's a struct - options = options ?? new FindOneAndReplaceOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options, renderArgs); + var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options); return ExecuteWriteOperation(session, operation, cancellationToken); } - public override Task FindOneAndReplaceAsync(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task FindOneAndReplaceAsync(FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => FindOneAndReplaceAsync(session, filter, replacement, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await FindOneAndReplaceAsync(session, filter, replacement, options, cancellationToken).ConfigureAwait(false); } - public override Task FindOneAndReplaceAsync(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task FindOneAndReplaceAsync(IClientSessionHandle session, FilterDefinition filter, TDocument replacement, FindOneAndReplaceOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); var replacementObject = Ensure.IsNotNull((object)replacement, nameof(replacement)); // only box once if it's a struct - options = options ?? new FindOneAndReplaceOptions(); - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options, renderArgs); + var operation = CreateFindOneAndReplaceOperation(filter, replacementObject, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } - public override TProjection FindOneAndUpdate(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndUpdate(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => FindOneAndUpdate(session, filter, update, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return FindOneAndUpdate(session, filter, update, options, cancellationToken); } - public override TProjection FindOneAndUpdate(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override TProjection FindOneAndUpdate(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); Ensure.IsNotNull(update, nameof(update)); - options = options ?? new FindOneAndUpdateOptions(); + options ??= new FindOneAndUpdateOptions(); if (update is PipelineUpdateDefinition && (options.ArrayFilters != null && options.ArrayFilters.Any())) { throw new NotSupportedException("An arrayfilter is not supported in the pipeline-style update."); } - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndUpdateOperation(filter, update, options, renderArgs); + var operation = CreateFindOneAndUpdateOperation(filter, update, options); return ExecuteWriteOperation(session, operation, cancellationToken); } - public override Task FindOneAndUpdateAsync(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task FindOneAndUpdateAsync(FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => FindOneAndUpdateAsync(session, filter, update, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await FindOneAndUpdateAsync(session, filter, update, options, cancellationToken).ConfigureAwait(false); } - public override Task FindOneAndUpdateAsync(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task FindOneAndUpdateAsync(IClientSessionHandle session, FilterDefinition filter, UpdateDefinition update, FindOneAndUpdateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(filter, nameof(filter)); Ensure.IsNotNull(update, nameof(update)); - options = options ?? new FindOneAndUpdateOptions(); - + options ??= new FindOneAndUpdateOptions(); if (update is PipelineUpdateDefinition && (options.ArrayFilters != null && options.ArrayFilters.Any())) { throw new NotSupportedException("An arrayfilter is not supported in the pipeline-style update."); } - var renderArgs = GetRenderArgs(); - var operation = CreateFindOneAndUpdateOperation(filter, update, options, renderArgs); + var operation = CreateFindOneAndUpdateOperation(filter, update, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } [Obsolete("Use Aggregation pipeline instead.")] - public override IAsyncCursor MapReduce(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor MapReduce(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => MapReduce(session, map, reduce, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return MapReduce(session, map, reduce, options, cancellationToken: cancellationToken); } [Obsolete("Use Aggregation pipeline instead.")] - public override IAsyncCursor MapReduce(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor MapReduce(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(map, nameof(map)); Ensure.IsNotNull(reduce, nameof(reduce)); - options = options ?? new MapReduceOptions(); + options ??= new MapReduceOptions(); var outputOptions = options.OutputOptions ?? MapReduceOutputOptions.Inline; var resultSerializer = ResolveResultSerializer(options.ResultSerializer); @@ -615,32 +557,24 @@ public override MongoCollectionSettings Settings { var mapReduceOperation = CreateMapReduceOutputToCollectionOperation(map, reduce, options, outputOptions, renderArgs); ExecuteWriteOperation(session, mapReduceOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateMapReduceOutputToCollectionFindOperation(options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + return CreateMapReduceOutputToCollectionResultCursor(session, options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); } } [Obsolete("Use Aggregation pipeline instead.")] - public override Task> MapReduceAsync(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> MapReduceAsync(BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => MapReduceAsync(session, map, reduce, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await MapReduceAsync(session, map, reduce, options, cancellationToken).ConfigureAwait(false); } [Obsolete("Use Aggregation pipeline instead.")] - public override async Task> MapReduceAsync(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> MapReduceAsync(IClientSessionHandle session, BsonJavaScript map, BsonJavaScript reduce, MapReduceOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(map, nameof(map)); Ensure.IsNotNull(reduce, nameof(reduce)); - options = options ?? new MapReduceOptions(); + options ??= new MapReduceOptions(); var outputOptions = options.OutputOptions ?? MapReduceOutputOptions.Inline; var resultSerializer = ResolveResultSerializer(options.ResultSerializer); @@ -655,16 +589,7 @@ public override MongoCollectionSettings Settings { var mapReduceOperation = CreateMapReduceOutputToCollectionOperation(map, reduce, options, outputOptions, renderArgs); await ExecuteWriteOperationAsync(session, mapReduceOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateMapReduceOutputToCollectionFindOperation(options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult(deferredCursor).ConfigureAwait(false); + return CreateMapReduceOutputToCollectionResultCursor(session, options, mapReduceOperation.OutputCollectionNamespace, resultSerializer); } } @@ -685,42 +610,44 @@ public override IFilteredMongoCollection OfType Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Watch(session, pipeline, options, cancellationToken); } public override IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _database.Client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public override Task> WatchAsync( + public override async Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await WatchAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } public override Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _database.Client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } @@ -850,72 +777,11 @@ private AggregateOperation CreateAggregateOperation(RenderedPi }; } - private FindOperation CreateAggregateToCollectionFindOperation(BsonDocument outStage, IBsonSerializer resultSerializer, AggregateOptions options) + private IAsyncCursor CreateAggregateToCollectionResultCursor(IClientSessionHandle session, RenderedPipelineDefinition pipeline, AggregateOptions options) { - CollectionNamespace outputCollectionNamespace; - var stageName = outStage.GetElement(0).Name; - switch (stageName) - { - case "$out": - { - var outValue = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (outValue.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = outValue.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); - outputCollectionName = outValue["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - case "$merge": - { - var mergeArguments = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (mergeArguments.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = mergeArguments.AsString; - } - else - { - var into = mergeArguments.AsBsonDocument["into"]; - if (into.IsString) - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - outputCollectionName = into.AsString; - } - else - { - if (into.AsBsonDocument.Contains("db")) - { - outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); - } - else - { - outputDatabaseNamespace = _collectionNamespace.DatabaseNamespace; - } - outputCollectionName = into["coll"].AsString; - } - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - default: - throw new ArgumentException($"Unexpected stage name: {stageName}."); - } + var outputCollectionNamespace = AggregateHelper.GetOutCollection(pipeline.Documents.Last(), _collectionNamespace.DatabaseNamespace); - return new FindOperation( - outputCollectionNamespace, - resultSerializer, - _messageEncoderSettings) + var findOperation = new FindOperation(outputCollectionNamespace, pipeline.OutputSerializer, _messageEncoderSettings) { BatchSize = options.BatchSize, Collation = options.Collation, @@ -923,6 +789,16 @@ private FindOperation CreateAggregateToCollectionFindOperation ReadConcern = _settings.ReadConcern, RetryRequested = _database.Client.Settings.RetryReads }; + + // we want to delay execution of the find because the user may + // not want to iterate the results at all... + var forkedSession = session.Fork(); + var readOperationOptions = _readOperationOptions with { ExplicitReadPreference = ReadPreference.Primary }; + var deferredCursor = new DeferredAsyncCursor( + () => forkedSession.Dispose(), + ct => ExecuteReadOperation(forkedSession, findOperation, readOperationOptions, ct), + ct => ExecuteReadOperationAsync(forkedSession, findOperation, readOperationOptions, ct)); + return deferredCursor; } private AggregateToCollectionOperation CreateAggregateToCollectionOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) @@ -947,15 +823,22 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation> requests, - BulkWriteOptions options, - RenderArgs renderArgs) + IReadOnlyList> requests, + BulkWriteOptions options) { + options ??= new BulkWriteOptions(); + var renderArgs = GetRenderArgs(); var effectiveWriteConcern = session.IsInTransaction ? WriteConcern.Acknowledged : _settings.WriteConcern; + var writeModels = requests.Select((model, index) => + { + model.ThrowIfNotValid(); + return ConvertWriteModelToWriteRequest(model, index, renderArgs); + }).ToArray(); + return new BulkMixedWriteOperation( _collectionNamespace, - requests.Select((model, index) => ConvertWriteModelToWriteRequest(model, index, renderArgs)), + writeModels, _messageEncoderSettings) { BypassDocumentValidation = options.BypassDocumentValidation, @@ -969,9 +852,10 @@ private BulkMixedWriteOperation CreateBulkWriteOperation( private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) + ChangeStreamOptions options) { + var translationOptions = _database.Client.Settings.TranslationOptions; + return ChangeStreamHelper.CreateChangeStreamOperation( this, pipeline, @@ -984,9 +868,11 @@ private ChangeStreamOperation CreateChangeStreamOperation( private CountDocumentsOperation CreateCountDocumentsOperation( FilterDefinition filter, - CountOptions options, - RenderArgs renderArgs) + CountOptions options) { + options ??= new CountOptions(); + var renderArgs = GetRenderArgs(); + return new CountDocumentsOperation(_collectionNamespace, _messageEncoderSettings) { Collation = options.Collation, @@ -1003,9 +889,11 @@ private CountDocumentsOperation CreateCountDocumentsOperation( private CountOperation CreateCountOperation( FilterDefinition filter, - CountOptions options, - RenderArgs renderArgs) + CountOptions options) { + options ??= new CountOptions(); + var renderArgs = GetRenderArgs(); + return new CountOperation(_collectionNamespace, _messageEncoderSettings) { Collation = options.Collation, @@ -1023,9 +911,10 @@ private CountOperation CreateCountOperation( private DistinctOperation CreateDistinctOperation( FieldDefinition field, FilterDefinition filter, - DistinctOptions options, - RenderArgs renderArgs) + DistinctOptions options) { + options ??= new DistinctOptions(); + var renderArgs = GetRenderArgs(); var renderedField = field.Render(renderArgs); var valueSerializer = GetValueSerializerForDistinct(renderedField, _settings.SerializerRegistry); @@ -1047,9 +936,10 @@ private DistinctOperation CreateDistinctOperation( private DistinctOperation CreateDistinctManyOperation( FieldDefinition> field, FilterDefinition filter, - DistinctOptions options, - RenderArgs renderArgs) + DistinctOptions options) { + options ??= new DistinctOptions(); + var renderArgs = GetRenderArgs(); var renderedField = field.Render(renderArgs); var itemSerializer = GetItemSerializerForDistinctMany(renderedField, _settings.SerializerRegistry); @@ -1080,9 +970,10 @@ private EstimatedDocumentCountOperation CreateEstimatedDocumentCountOperation(Es private FindOneAndDeleteOperation CreateFindOneAndDeleteOperation( FilterDefinition filter, - FindOneAndDeleteOptions options, - RenderArgs renderArgs) + FindOneAndDeleteOptions options) { + options ??= new FindOneAndDeleteOptions(); + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1106,17 +997,19 @@ private FindOneAndDeleteOperation CreateFindOneAndDeleteOperation CreateFindOneAndReplaceOperation( FilterDefinition filter, - object replacementObject, - FindOneAndReplaceOptions options, - RenderArgs renderArgs) + object replacement, + FindOneAndReplaceOptions options) { + options ??= new FindOneAndReplaceOptions(); + + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); return new FindOneAndReplaceOperation( _collectionNamespace, filter.Render(renderArgs), - new BsonDocumentWrapper(replacementObject, _documentSerializer), + new BsonDocumentWrapper(replacement, _documentSerializer), new FindAndModifyValueDeserializer(renderedProjection.ProjectionSerializer), _messageEncoderSettings) { @@ -1138,9 +1031,9 @@ private FindOneAndReplaceOperation CreateFindOneAndReplaceOperation private FindOneAndUpdateOperation CreateFindOneAndUpdateOperation( FilterDefinition filter, UpdateDefinition update, - FindOneAndUpdateOptions options, - RenderArgs renderArgs) + FindOneAndUpdateOptions options) { + var renderArgs = GetRenderArgs(); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1169,9 +1062,11 @@ private FindOneAndUpdateOperation CreateFindOneAndUpdateOperation CreateFindOperation( FilterDefinition filter, - FindOptions options, - RenderArgs renderArgs) + FindOptions options) { + options ??= new FindOptions(); + + var renderArgs = GetRenderArgs(options.TranslationOptions); var projection = options.Projection ?? new ClientSideDeserializationProjectionDefinition(); var renderedProjection = projection.Render(renderArgs with { RenderForFind = true }); @@ -1285,10 +1180,10 @@ private MapReduceOutputToCollectionOperation CreateMapReduceOutputToCollectionOp } #pragma warning disable CS0618 // Type or member is obsolete - private FindOperation CreateMapReduceOutputToCollectionFindOperation(MapReduceOptions options, CollectionNamespace outputCollectionNamespace, IBsonSerializer resultSerializer) + private IAsyncCursor CreateMapReduceOutputToCollectionResultCursor(IClientSessionHandle session, MapReduceOptions options, CollectionNamespace outputCollectionNamespace, IBsonSerializer resultSerializer) #pragma warning restore CS0618 // Type or member is obsolete { - return new FindOperation( + var findOperation = new FindOperation( outputCollectionNamespace, resultSerializer, _messageEncoderSettings) @@ -1298,22 +1193,36 @@ private FindOperation CreateMapReduceOutputToCollectionFindOperation( + () => forkedSession.Dispose(), + ct => ExecuteReadOperation(forkedSession, findOperation, readOperationOptions, ct), + ct => ExecuteReadOperationAsync(forkedSession, findOperation, readOperationOptions, ct)); + return deferredCursor; } - private IReadBindingHandle CreateReadBinding(IClientSessionHandle session, ReadPreference readPreference) - { - if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) - { - throw new InvalidOperationException("Read preference in a transaction must be primary."); - } + private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => ExecuteReadOperation(session, operation, _readOperationOptions, cancellationToken); - return ChannelPinningHelper.CreateReadBinding(_cluster, session.WrappedCoreSession.Fork(), readPreference); - } + private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadOperationOptions options, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperation(session, operation, options, true, cancellationToken); + + private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => ExecuteReadOperationAsync(session, operation, _readOperationOptions, cancellationToken); + + private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadOperationOptions options, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperationAsync(session, operation, options, true, cancellationToken); + + private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperation(session, operation, _writeOperationOptions, true, cancellationToken); + + private Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperationAsync(session, operation, _writeOperationOptions, true, cancellationToken); - private IWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) - { - return ChannelPinningHelper.CreateReadWriteBinding(_cluster, session.WrappedCoreSession.Fork()); - } private MessageEncoderSettings GetMessageEncoderSettings() { @@ -1389,50 +1298,6 @@ private RenderArgs GetRenderArgs(ExpressionTranslationOptions transla return new RenderArgs(_documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); } - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); - } - - private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } - - private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); - } - - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - - private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); - } - } - - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var binding = CreateReadWriteBinding(session)) - { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } - } - private IEnumerable RenderArrayFilters(IEnumerable arrayFilters) { if (arrayFilters == null) @@ -1465,38 +1330,6 @@ private IBsonSerializer ResolveResultSerializer(IBsonSerialize return _settings.SerializerRegistry.GetSerializer(); } - private void UsingImplicitSession(Action func, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - func(session); - } - } - - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - return func(session); - } - } - - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - await funcAsync(session).ConfigureAwait(false); - } - } - - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - return await funcAsync(session).ConfigureAwait(false); - } - } - // nested types private class MongoIndexManager : MongoIndexManagerBase { @@ -1526,92 +1359,77 @@ public override MongoCollectionSettings Settings } // public methods - public override IEnumerable CreateMany(IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateMany(models, null, cancellationToken); - } + public override IEnumerable CreateMany(IEnumerable> models, CancellationToken cancellationToken = default) + => CreateMany(models, null, cancellationToken: cancellationToken); public override IEnumerable CreateMany( IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSession(session => CreateMany(session, models, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + return CreateMany(session, models, options, cancellationToken); } - public override IEnumerable CreateMany(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateMany(session, models, null, cancellationToken); - } + public override IEnumerable CreateMany(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default) + => CreateMany(session, models, null, cancellationToken: cancellationToken); public override IEnumerable CreateMany( IClientSessionHandle session, IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(models, nameof(models)); - var renderArgs = _collection.GetRenderArgs(); - var requests = CreateCreateIndexRequests(models, renderArgs); - var operation = CreateCreateIndexesOperation(requests, options); + var operation = CreateCreateIndexesOperation(models, options); _collection.ExecuteWriteOperation(session, operation, cancellationToken); - - return requests.Select(x => x.GetIndexName()); + return operation.Requests.Select(x => x.GetIndexName()); } - public override Task> CreateManyAsync(IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateManyAsync(models, null, cancellationToken); - } + public override Task> CreateManyAsync(IEnumerable> models, CancellationToken cancellationToken = default) + => CreateManyAsync(models, null, cancellationToken: cancellationToken); - public override Task> CreateManyAsync( + public override async Task> CreateManyAsync( IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSessionAsync(session => CreateManyAsync(session, models, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + return await CreateManyAsync(session, models, options, cancellationToken).ConfigureAwait(false); } - public override Task> CreateManyAsync(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default(CancellationToken)) - { - return CreateManyAsync(session, models, null, cancellationToken); - } + public override Task> CreateManyAsync(IClientSessionHandle session, IEnumerable> models, CancellationToken cancellationToken = default) + => CreateManyAsync(session, models, null, cancellationToken: cancellationToken); public override async Task> CreateManyAsync( IClientSessionHandle session, IEnumerable> models, CreateManyIndexesOptions options, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(models, nameof(models)); - var renderArgs = _collection.GetRenderArgs(); - var requests = CreateCreateIndexRequests(models, renderArgs); - var operation = CreateCreateIndexesOperation(requests, options); + var operation = CreateCreateIndexesOperation(models, options); await _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); - - return requests.Select(x => x.GetIndexName()); + return operation.Requests.Select(x => x.GetIndexName()); } public override void DropAll(CancellationToken cancellationToken) - { - _collection.UsingImplicitSession(session => DropAll(session, cancellationToken), cancellationToken); - } + => DropAll(options: null, cancellationToken: cancellationToken); - public override void DropAll(DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void DropAll(DropIndexOptions options, CancellationToken cancellationToken = default) { - _collection.UsingImplicitSession(session => DropAll(session, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + DropAll(session, options, cancellationToken); } - public override void DropAll(IClientSessionHandle session, CancellationToken cancellationToken = default(CancellationToken)) - { - DropAll(session, null, cancellationToken); - } + public override void DropAll(IClientSessionHandle session, CancellationToken cancellationToken = default) + => DropAll(session, null, cancellationToken); - public override void DropAll(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void DropAll(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); var operation = CreateDropAllOperation(options); @@ -1619,138 +1437,117 @@ public override void DropAll(CancellationToken cancellationToken) } public override Task DropAllAsync(CancellationToken cancellationToken) - { - return _collection.UsingImplicitSessionAsync(session => DropAllAsync(session, cancellationToken), cancellationToken); - } + => DropAllAsync(options: null, cancellationToken); - public override Task DropAllAsync(DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task DropAllAsync(DropIndexOptions options, CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSessionAsync(session => DropAllAsync(session, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + await DropAllAsync(session, options, cancellationToken).ConfigureAwait(false); } - public override Task DropAllAsync(IClientSessionHandle session, CancellationToken cancellationToken = default(CancellationToken)) - { - return DropAllAsync(session, null, cancellationToken); - } + public override Task DropAllAsync(IClientSessionHandle session, CancellationToken cancellationToken = default) + => DropAllAsync(session, null, cancellationToken); - public override Task DropAllAsync(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task DropAllAsync(IClientSessionHandle session, DropIndexOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); var operation = CreateDropAllOperation(options); return _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken); } - public override void DropOne(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - _collection.UsingImplicitSession(session => DropOne(session, name, cancellationToken), cancellationToken); - } + public override void DropOne(string name, CancellationToken cancellationToken = default) + => DropOne(name, null, cancellationToken); - public override void DropOne(string name, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override void DropOne(string name, DropIndexOptions options, CancellationToken cancellationToken = default) { - _collection.UsingImplicitSession(session => DropOne(session, name, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + DropOne(session, name, options, cancellationToken); } - public override void DropOne(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - DropOne(session, name, null, cancellationToken); - } + public override void DropOne(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => DropOne(session, name, null, cancellationToken); - public override void DropOne( - IClientSessionHandle session, - string name, - DropIndexOptions options, - CancellationToken cancellationToken) + public override void DropOne(IClientSessionHandle session, string name, DropIndexOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(name, nameof(name)); if (name == "*") { - throw new ArgumentException("Cannot specify '*' for the index name. Use DropAllAsync to drop all indexes.", "name"); + throw new ArgumentException($"Cannot specify '*' for the index name. Use {nameof(DropAll)} to drop all indexes.", nameof(name)); } var operation = CreateDropOneOperation(name, options); _collection.ExecuteWriteOperation(session, operation, cancellationToken); } - public override Task DropOneAsync(string name, CancellationToken cancellationToken = default(CancellationToken)) - { - return _collection.UsingImplicitSessionAsync(session => DropOneAsync(session, name, cancellationToken), cancellationToken); - } + public override Task DropOneAsync(string name, CancellationToken cancellationToken = default) + => DropOneAsync(name, null, cancellationToken); - public override Task DropOneAsync(string name, DropIndexOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task DropOneAsync(string name, DropIndexOptions options, CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSessionAsync(session => DropOneAsync(session, name, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + await DropOneAsync(session, name, options, cancellationToken).ConfigureAwait(false); } - public override Task DropOneAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - return DropOneAsync(session, name, null, cancellationToken); - } + public override Task DropOneAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken = default) + => DropOneAsync(session, name, null, cancellationToken); - public override Task DropOneAsync( - IClientSessionHandle session, - string name, - DropIndexOptions options, - CancellationToken cancellationToken) + public override Task DropOneAsync(IClientSessionHandle session, string name, DropIndexOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(name, nameof(name)); if (name == "*") { - throw new ArgumentException("Cannot specify '*' for the index name. Use DropAllAsync to drop all indexes.", "name"); + throw new ArgumentException($"Cannot specify '*' for the index name. Use {nameof(DropAllAsync)} to drop all indexes.", nameof(name)); } var operation = CreateDropOneOperation(name, options); return _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken); } - public override IAsyncCursor List(CancellationToken cancellationToken = default(CancellationToken)) - { - return List(options: null, cancellationToken); - } + public override IAsyncCursor List(CancellationToken cancellationToken = default) + => List(options: null, cancellationToken); - public override IAsyncCursor List(ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor List(ListIndexesOptions options, CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSession(session => List(session, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + return List(session, options, cancellationToken); } public override IAsyncCursor List(IClientSessionHandle session, CancellationToken cancellationToken = default) - { - return List(session, options: null, cancellationToken); - } + => List(session, options: null, cancellationToken); - public override IAsyncCursor List(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override IAsyncCursor List(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); var operation = CreateListIndexesOperation(options); - return _collection.ExecuteReadOperation(session, operation, ReadPreference.Primary, cancellationToken); + return _collection.ExecuteReadOperation(session, operation, cancellationToken); } - public override Task> ListAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - return ListAsync(options: null, cancellationToken); - } + public override Task> ListAsync(CancellationToken cancellationToken = default) + => ListAsync(options: null, cancellationToken); - public override Task> ListAsync(ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override async Task> ListAsync(ListIndexesOptions options, CancellationToken cancellationToken = default) { - return _collection.UsingImplicitSessionAsync(session => ListAsync(session, options, cancellationToken), cancellationToken); + using var session = _collection._operationExecutor.StartImplicitSession(); + return await ListAsync(session, options, cancellationToken).ConfigureAwait(false); } public override Task> ListAsync(IClientSessionHandle session, CancellationToken cancellationToken = default) - { - return ListAsync(session, options: null, cancellationToken); - } + => ListAsync(session, options: null, cancellationToken); - public override Task> ListAsync(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public override Task> ListAsync(IClientSessionHandle session, ListIndexesOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); var operation = CreateListIndexesOperation(options); - return _collection.ExecuteReadOperationAsync(session, operation, ReadPreference.Primary, cancellationToken); + return _collection.ExecuteReadOperationAsync(session, operation, cancellationToken); } // private methods - private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable requests, CreateManyIndexesOptions options) + private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable> models, CreateManyIndexesOptions options) { + var requests = CreateCreateIndexRequests(models); + return new CreateIndexesOperation(_collection._collectionNamespace, requests, _collection._messageEncoderSettings) { Comment = options?.Comment, @@ -1760,8 +1557,9 @@ private CreateIndexesOperation CreateCreateIndexesOperation(IEnumerable CreateCreateIndexRequests(IEnumerable> models, RenderArgs renderArgs) + private IEnumerable CreateCreateIndexRequests(IEnumerable> models) { + var renderArgs = _collection.GetRenderArgs(); return models.Select(m => { var options = m.Options ?? new CreateIndexOptions(); @@ -1841,20 +1639,18 @@ public MongoSearchIndexManager(MongoCollectionImpl collection) public IEnumerable CreateMany(IEnumerable models, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); - var result = _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); - var indexNames = GetIndexNames(result); - - return indexNames; + var result = _collection.ExecuteWriteOperation(session, operation, cancellationToken); + return GetIndexNames(result); } public async Task> CreateManyAsync(IEnumerable models, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = CreateCreateIndexesOperation(models); - var result = await _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken).ConfigureAwait(false); - var indexNames = GetIndexNames(result); - - return indexNames; + var result = await _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); + return GetIndexNames(result); } public string CreateOne(BsonDocument definition, string name = null, CancellationToken cancellationToken = default) => @@ -1877,14 +1673,16 @@ public async Task CreateOneAsync(CreateSearchIndexModel model, Cancellat public void DropOne(string indexName, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings); - _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); + _collection.ExecuteWriteOperation(session, operation, cancellationToken); } - public Task DropOneAsync(string indexName, CancellationToken cancellationToken = default) + public async Task DropOneAsync(string indexName, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = new DropSearchIndexOperation(_collection.CollectionNamespace, indexName, _collection._messageEncoderSettings); - return _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken); + await _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); } public IAsyncCursor List(string indexName, AggregateOptions aggregateOptions = null, CancellationToken cancellationToken = default) @@ -1899,16 +1697,16 @@ public Task> ListAsync(string indexName, AggregateOpt public void Update(string indexName, BsonDocument definition, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings); - - _collection.UsingImplicitSession(session => _collection.ExecuteWriteOperation(session, operation, cancellationToken), cancellationToken); + _collection.ExecuteWriteOperation(session, operation, cancellationToken); } public async Task UpdateAsync(string indexName, BsonDocument definition, CancellationToken cancellationToken = default) { + using var session = _collection._operationExecutor.StartImplicitSession(); var operation = new UpdateSearchIndexOperation(_collection.CollectionNamespace, indexName, definition, _collection._messageEncoderSettings); - - await _collection.UsingImplicitSessionAsync(session => _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken), cancellationToken).ConfigureAwait(false); + await _collection.ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); } // private methods diff --git a/src/MongoDB.Driver/MongoDatabase.cs b/src/MongoDB.Driver/MongoDatabase.cs index a57eec55d94..cb1e0820dff 100644 --- a/src/MongoDB.Driver/MongoDatabase.cs +++ b/src/MongoDB.Driver/MongoDatabase.cs @@ -23,8 +23,6 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -using MongoDB.Driver.Core; -using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; @@ -41,6 +39,8 @@ internal sealed class MongoDatabase : IMongoDatabase private readonly DatabaseNamespace _databaseNamespace; private readonly IOperationExecutor _operationExecutor; private readonly MongoDatabaseSettings _settings; + private readonly ReadOperationOptions _readOperationOptions; + private readonly WriteOperationOptions _writeOperationOptions; // constructors public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, MongoDatabaseSettings settings, IClusterInternal cluster, IOperationExecutor operationExecutor) @@ -50,6 +50,9 @@ public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, M _settings = Ensure.IsNotNull(settings, nameof(settings)).Freeze(); _cluster = Ensure.IsNotNull(cluster, nameof(cluster)); _operationExecutor = Ensure.IsNotNull(operationExecutor, nameof(operationExecutor)); + + _readOperationOptions = new(DefaultReadPreference: _settings.ReadPreference); + _writeOperationOptions = new(); } // public properties @@ -58,34 +61,25 @@ public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, M public MongoDatabaseSettings Settings => _settings; // public methods - public IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor Aggregate(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Aggregate(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Aggregate(session, pipeline, options, cancellationToken: cancellationToken); } - public IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor Aggregate(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return deferredCursor; + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { @@ -94,34 +88,25 @@ public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, M } } - public Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> AggregateAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await AggregateAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> AggregateAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage != null && (lastStageName == "$out" || lastStageName == "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (isAggregateToCollection) { var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - - // we want to delay execution of the find because the user may - // not want to iterate the results at all... - var findOperation = CreateAggregateToCollectionFindOperation(lastStage, renderedPipeline.OutputSerializer, options); - var forkedSession = session.Fork(); - var deferredCursor = new DeferredAsyncCursor( - () => forkedSession.Dispose(), - ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct), - ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct)); - return await Task.FromResult>(deferredCursor).ConfigureAwait(false); + return CreateAggregateToCollectionResultCursor(session, renderedPipeline, options); } else { @@ -130,59 +115,56 @@ public MongoDatabase(IMongoClient client, DatabaseNamespace databaseNamespace, M } } - public void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public void AggregateToCollection(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => AggregateToCollection(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + AggregateToCollection(session, pipeline, options, cancellationToken); } - public void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public void AggregateToCollection(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (!isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollection requires that the last stage be $out or $merge."); } - else - { - var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - ExecuteWriteOperation(session, aggregateOperation, cancellationToken); - } + + var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); + ExecuteWriteOperation(session, aggregateOperation, cancellationToken); } - public Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public async Task AggregateToCollectionAsync(PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => AggregateToCollectionAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await AggregateToCollectionAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } - public async Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default(CancellationToken)) + public Task AggregateToCollectionAsync(IClientSessionHandle session, PipelineDefinition pipeline, AggregateOptions options, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options?.TranslationOptions); - var renderedPipeline = Ensure.IsNotNull(pipeline, nameof(pipeline)).Render(renderArgs); - options = options ?? new AggregateOptions(); + Ensure.IsNotNull(pipeline, nameof(pipeline)); + options ??= new AggregateOptions(); - var lastStage = renderedPipeline.Documents.LastOrDefault(); - var lastStageName = lastStage?.GetElement(0).Name; - if (lastStage == null || (lastStageName != "$out" && lastStageName != "$merge")) + var renderArgs = GetRenderArgs(NoPipelineInputSerializer.Instance, options.TranslationOptions); + var renderedPipeline = AggregateHelper.RenderAggregatePipeline(pipeline, renderArgs, out var isAggregateToCollection); + if (!isAggregateToCollection) { throw new InvalidOperationException("AggregateToCollectionAsync requires that the last stage be $out or $merge."); } - else - { - var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); - await ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken).ConfigureAwait(false); - } + + var aggregateOperation = CreateAggregateToCollectionOperation(renderedPipeline, options); + return ExecuteWriteOperationAsync(session, aggregateOperation, cancellationToken); } public void CreateCollection(string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - UsingImplicitSession(session => CreateCollection(session, name, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + CreateCollection(session, name, options, cancellationToken); } public void CreateCollection(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) @@ -203,7 +185,7 @@ public void CreateCollection(IClientSessionHandle session, string name, CreateCo return; } - var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod("CreateCollectionHelper", BindingFlags.NonPublic | BindingFlags.Instance); + var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod(nameof(CreateCollectionHelper), BindingFlags.NonPublic | BindingFlags.Instance); var documentType = options.GetType().GetTypeInfo().GetGenericArguments()[0]; var methodInfo = genericMethodDefinition.MakeGenericMethod(documentType); try @@ -216,9 +198,10 @@ public void CreateCollection(IClientSessionHandle session, string name, CreateCo } } - public Task CreateCollectionAsync(string name, CreateCollectionOptions options, CancellationToken cancellationToken) + public async Task CreateCollectionAsync(string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => CreateCollectionAsync(session, name, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await CreateCollectionAsync(session, name, options, cancellationToken).ConfigureAwait(false); } public async Task CreateCollectionAsync(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) @@ -239,7 +222,7 @@ public async Task CreateCollectionAsync(IClientSessionHandle session, string nam return; } - var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod("CreateCollectionHelperAsync", BindingFlags.NonPublic | BindingFlags.Instance); + var genericMethodDefinition = typeof(MongoDatabase).GetTypeInfo().GetMethod(nameof(CreateCollectionHelperAsync), BindingFlags.NonPublic | BindingFlags.Instance); var documentType = options.GetType().GetTypeInfo().GetGenericArguments()[0]; var methodInfo = genericMethodDefinition.MakeGenericMethod(documentType); try @@ -252,37 +235,37 @@ public async Task CreateCollectionAsync(IClientSessionHandle session, string nam } } - public void CreateView(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public void CreateView(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => CreateView(session, viewName, viewOn, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + CreateView(session, viewName, viewOn, pipeline, options, cancellationToken); } - public void CreateView(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public void CreateView(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(viewName, nameof(viewName)); Ensure.IsNotNull(viewOn, nameof(viewOn)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new CreateViewOptions(); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options, translationOptions); + + var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options); ExecuteWriteOperation(session, operation, cancellationToken); } - public Task CreateViewAsync(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CreateViewAsync(string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => CreateViewAsync(session, viewName, viewOn, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await CreateViewAsync(session, viewName, viewOn, pipeline, options, cancellationToken).ConfigureAwait(false); } - public Task CreateViewAsync(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateViewAsync(IClientSessionHandle session, string viewName, string viewOn, PipelineDefinition pipeline, CreateViewOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(viewName, nameof(viewName)); Ensure.IsNotNull(viewOn, nameof(viewOn)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - options = options ?? new CreateViewOptions(); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options, translationOptions); + + var operation = CreateCreateViewOperation(viewName, viewOn, pipeline, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } @@ -293,7 +276,8 @@ public void DropCollection(string name, CancellationToken cancellationToken) public void DropCollection(string name, DropCollectionOptions options, CancellationToken cancellationToken = default) { - UsingImplicitSession(session => DropCollection(session, name, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + DropCollection(session, name, options, cancellationToken); } public void DropCollection(IClientSessionHandle session, string name, CancellationToken cancellationToken) @@ -305,30 +289,33 @@ public void DropCollection(IClientSessionHandle session, string name, DropCollec { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(name, nameof(name)); - var operation = CreateDropCollectionOperation(name, options, session, cancellationToken); + + var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); + var encryptedFields = GetEffectiveEncryptedFields(session, collectionNamespace, options, cancellationToken); + var operation = CreateDropCollectionOperation(collectionNamespace, encryptedFields); ExecuteWriteOperation(session, operation, cancellationToken); } public Task DropCollectionAsync(string name, CancellationToken cancellationToken) - { - return DropCollectionAsync(name, options: null, cancellationToken); - } + => DropCollectionAsync(name, options: null, cancellationToken); - public Task DropCollectionAsync(string name, DropCollectionOptions options, CancellationToken cancellationToken) + public async Task DropCollectionAsync(string name, DropCollectionOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => DropCollectionAsync(session, name, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await DropCollectionAsync(session, name, options, cancellationToken).ConfigureAwait(false); } public Task DropCollectionAsync(IClientSessionHandle session, string name, CancellationToken cancellationToken) - { - return DropCollectionAsync(session, name, options: null, cancellationToken); - } + => DropCollectionAsync(session, name, options: null, cancellationToken); public async Task DropCollectionAsync(IClientSessionHandle session, string name, DropCollectionOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(name, nameof(name)); - var operation = await CreateDropCollectionOperationAsync(name, options, session, cancellationToken).ConfigureAwait(false); + + var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); + var encryptedFields = await GetEffectiveEncryptedFieldsAsync(session, collectionNamespace, options, cancellationToken).ConfigureAwait(false); + var operation = CreateDropCollectionOperation(collectionNamespace, encryptedFields); await ExecuteWriteOperationAsync(session, operation, cancellationToken).ConfigureAwait(false); } @@ -345,67 +332,64 @@ public IMongoCollection GetCollection(string name, MongoCo return new MongoCollectionImpl(this, new CollectionNamespace(_databaseNamespace, name), settings, _cluster, _operationExecutor); } - public IAsyncCursor ListCollectionNames(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor ListCollectionNames(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => ListCollectionNames(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return ListCollectionNames(session, options, cancellationToken); } - public IAsyncCursor ListCollectionNames(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public IAsyncCursor ListCollectionNames(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionNamesOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - var cursor = ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); + var operation = CreateListCollectionNamesOperation(options); + var cursor = ExecuteReadOperation(session, operation, _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, cancellationToken); return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } - public Task> ListCollectionNamesAsync(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> ListCollectionNamesAsync(ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => ListCollectionNamesAsync(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await ListCollectionNamesAsync(session, options, cancellationToken).ConfigureAwait(false); } - public async Task> ListCollectionNamesAsync(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> ListCollectionNamesAsync(IClientSessionHandle session, ListCollectionNamesOptions options = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionNamesOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - var cursor = await ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken).ConfigureAwait(false); + var operation = CreateListCollectionNamesOperation(options); + var cursor = await ExecuteReadOperationAsync(session, operation, _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, cancellationToken).ConfigureAwait(false); return new BatchTransformingAsyncCursor(cursor, ExtractCollectionNames); } public IAsyncCursor ListCollections(ListCollectionsOptions options, CancellationToken cancellationToken) { - return UsingImplicitSession(session => ListCollections(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return ListCollections(session, options, cancellationToken); } public IAsyncCursor ListCollections(IClientSessionHandle session, ListCollectionsOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionsOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); + var operation = CreateListCollectionsOperation(options); + return ExecuteReadOperation(session, operation, _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, cancellationToken); } - public Task> ListCollectionsAsync(ListCollectionsOptions options, CancellationToken cancellationToken) + public async Task> ListCollectionsAsync(ListCollectionsOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => ListCollectionsAsync(session, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await ListCollectionsAsync(session, options, cancellationToken).ConfigureAwait(false); } public Task> ListCollectionsAsync(IClientSessionHandle session, ListCollectionsOptions options, CancellationToken cancellationToken) { Ensure.IsNotNull(session, nameof(session)); - var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); - var operation = CreateListCollectionsOperation(options, renderArgs); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, ReadPreference.Primary); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); + var operation = CreateListCollectionsOperation(options); + return ExecuteReadOperationAsync(session, operation, _readOperationOptions with { DefaultReadPreference = ReadPreference.Primary }, cancellationToken); } public void RenameCollection(string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) { - UsingImplicitSession(session => RenameCollection(session, oldName, newName, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + RenameCollection(session, oldName, newName, options, cancellationToken); } public void RenameCollection(IClientSessionHandle session, string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) @@ -413,15 +397,15 @@ public void RenameCollection(IClientSessionHandle session, string oldName, strin Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(oldName, nameof(oldName)); Ensure.IsNotNullOrEmpty(newName, nameof(newName)); - options = options ?? new RenameCollectionOptions(); var operation = CreateRenameCollectionOperation(oldName, newName, options); ExecuteWriteOperation(session, operation, cancellationToken); } - public Task RenameCollectionAsync(string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) + public async Task RenameCollectionAsync(string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) { - return UsingImplicitSessionAsync(session => RenameCollectionAsync(session, oldName, newName, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + await RenameCollectionAsync(session, oldName, newName, options, cancellationToken).ConfigureAwait(false); } public Task RenameCollectionAsync(IClientSessionHandle session, string oldName, string newName, RenameCollectionOptions options, CancellationToken cancellationToken) @@ -429,81 +413,82 @@ public Task RenameCollectionAsync(IClientSessionHandle session, string oldName, Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNullOrEmpty(oldName, nameof(oldName)); Ensure.IsNotNullOrEmpty(newName, nameof(newName)); - options = options ?? new RenameCollectionOptions(); var operation = CreateRenameCollectionOperation(oldName, newName, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } - public TResult RunCommand(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) + public TResult RunCommand(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => RunCommand(session, command, readPreference, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return RunCommand(session, command, readPreference, cancellationToken); } - public TResult RunCommand(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) + public TResult RunCommand(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(command, nameof(command)); var operation = CreateRunCommandOperation(command); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, readPreference, ReadPreference.Primary); - return ExecuteReadOperation(session, operation, effectiveReadPreference, cancellationToken); + return ExecuteReadOperation(session, operation, _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary }, cancellationToken); } - public Task RunCommandAsync(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task RunCommandAsync(Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => RunCommandAsync(session, command, readPreference, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await RunCommandAsync(session, command, readPreference, cancellationToken).ConfigureAwait(false); } - public Task RunCommandAsync(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default(CancellationToken)) + public Task RunCommandAsync(IClientSessionHandle session, Command command, ReadPreference readPreference = null, CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(command, nameof(command)); var operation = CreateRunCommandOperation(command); - var effectiveReadPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, readPreference, ReadPreference.Primary); - return ExecuteReadOperationAsync(session, operation, effectiveReadPreference, cancellationToken); + return ExecuteReadOperationAsync(session, operation, _readOperationOptions with { ExplicitReadPreference = readPreference, DefaultReadPreference = ReadPreference.Primary }, cancellationToken); } public IChangeStreamCursor Watch( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return UsingImplicitSession(session => Watch(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return Watch(session, pipeline, options, cancellationToken); } public IChangeStreamCursor Watch( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperation(session, operation, cancellationToken); } - public Task> WatchAsync( + public async Task> WatchAsync( PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { - return UsingImplicitSessionAsync(session => WatchAsync(session, pipeline, options, cancellationToken), cancellationToken); + using var session = _operationExecutor.StartImplicitSession(); + return await WatchAsync(session, pipeline, options, cancellationToken).ConfigureAwait(false); } public Task> WatchAsync( IClientSessionHandle session, PipelineDefinition, TResult> pipeline, ChangeStreamOptions options = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { Ensure.IsNotNull(session, nameof(session)); Ensure.IsNotNull(pipeline, nameof(pipeline)); - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateChangeStreamOperation(pipeline, options, translationOptions); + + var operation = CreateChangeStreamOperation(pipeline, options); return ExecuteReadOperationAsync(session, operation, cancellationToken); } @@ -532,6 +517,7 @@ public IMongoDatabase WithWriteConcern(WriteConcern writeConcern) } // private methods + private AggregateOperation CreateAggregateOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) { var messageEncoderSettings = GetMessageEncoderSettings(); @@ -557,59 +543,16 @@ private AggregateOperation CreateAggregateOperation(RenderedPi }; } - private FindOperation CreateAggregateToCollectionFindOperation(BsonDocument outStage, IBsonSerializer resultSerializer, AggregateOptions options) + private IAsyncCursor CreateAggregateToCollectionResultCursor(IClientSessionHandle session, RenderedPipelineDefinition pipeline, AggregateOptions options) { - CollectionNamespace outputCollectionNamespace; - var stageName = outStage.GetElement(0).Name; - switch (stageName) - { - case "$out": - { - var outValue = outStage[0]; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - if (outValue.IsString) - { - outputDatabaseNamespace = _databaseNamespace; - outputCollectionName = outValue.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(outValue["db"].AsString); - outputCollectionName = outValue["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - case "$merge": - { - var mergeArguments = outStage[0].AsBsonDocument; - DatabaseNamespace outputDatabaseNamespace; - string outputCollectionName; - var into = mergeArguments["into"]; - if (into.IsString) - { - outputDatabaseNamespace = _databaseNamespace; - outputCollectionName = into.AsString; - } - else - { - outputDatabaseNamespace = new DatabaseNamespace(into["db"].AsString); - outputCollectionName = into["coll"].AsString; - } - outputCollectionNamespace = new CollectionNamespace(outputDatabaseNamespace, outputCollectionName); - } - break; - default: - throw new ArgumentException($"Unexpected stage name: {stageName}."); - } + var outputCollectionNamespace = AggregateHelper.GetOutCollection(pipeline.Documents.Last(), _databaseNamespace); // because auto encryption is not supported for non-collection commands. // So, an error will be thrown in the previous CreateAggregateToCollectionOperation step. // However, since we've added encryption configuration for CreateAggregateToCollectionOperation operation, // it's not superfluous to also add it here var messageEncoderSettings = GetMessageEncoderSettings(); - return new FindOperation(outputCollectionNamespace, resultSerializer, messageEncoderSettings) + var findOperation = new FindOperation(outputCollectionNamespace, pipeline.OutputSerializer, messageEncoderSettings) { BatchSize = options.BatchSize, Collation = options.Collation, @@ -617,6 +560,16 @@ private FindOperation CreateAggregateToCollectionFindOperation ReadConcern = _settings.ReadConcern, RetryRequested = _client.Settings.RetryReads }; + + // we want to delay execution of the find because the user may + // not want to iterate the results at all... + var forkedSession = session.Fork(); + var readOperationOptions = _readOperationOptions with { ExplicitReadPreference = ReadPreference.Primary }; + var deferredCursor = new DeferredAsyncCursor( + () => forkedSession.Dispose(), + ct => ExecuteReadOperation(forkedSession, findOperation, readOperationOptions, ct), + ct => ExecuteReadOperationAsync(forkedSession, findOperation, readOperationOptions, ct)); + return deferredCursor; } private AggregateToCollectionOperation CreateAggregateToCollectionOperation(RenderedPipelineDefinition renderedPipeline, AggregateOptions options) @@ -642,24 +595,20 @@ private AggregateToCollectionOperation CreateAggregateToCollectionOperation(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - options = options ?? new CreateCollectionOptions(); - - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateCollectionOperation(name, options, translationOptions); + var operation = CreateCreateCollectionOperation(name, options); ExecuteWriteOperation(session, operation, cancellationToken); } private Task CreateCollectionHelperAsync(IClientSessionHandle session, string name, CreateCollectionOptions options, CancellationToken cancellationToken) { - options = options ?? new CreateCollectionOptions(); - - var translationOptions = _client.Settings.TranslationOptions; - var operation = CreateCreateCollectionOperation(name, options, translationOptions); + var operation = CreateCreateCollectionOperation(name, options); return ExecuteWriteOperationAsync(session, operation, cancellationToken); } - private IWriteOperation CreateCreateCollectionOperation(string name, CreateCollectionOptions options, ExpressionTranslationOptions translationOptions) + private IWriteOperation CreateCreateCollectionOperation(string name, CreateCollectionOptions options) { + options ??= new CreateCollectionOptions(); + var translationOptions = _client.Settings.TranslationOptions; var serializerRegistry = options.SerializerRegistry ?? BsonSerializer.SerializerRegistry; var documentSerializer = options.DocumentSerializer ?? serializerRegistry.GetSerializer(); @@ -700,9 +649,11 @@ private CreateViewOperation CreateCreateViewOperation( string viewName, string viewOn, PipelineDefinition pipeline, - CreateViewOptions options, - ExpressionTranslationOptions translationOptions) + CreateViewOptions options) { + options ??= new CreateViewOptions(); + + var translationOptions = _client.Settings.TranslationOptions; var serializerRegistry = options.SerializerRegistry ?? BsonSerializer.SerializerRegistry; var documentSerializer = options.DocumentSerializer ?? serializerRegistry.GetSerializer(); var pipelineDocuments = pipeline.Render(new (documentSerializer, serializerRegistry, translationOptions: translationOptions)).Documents; @@ -713,60 +664,8 @@ private CreateViewOperation CreateCreateViewOperation( }; } - private IWriteOperation CreateDropCollectionOperation(string name, DropCollectionOptions options, IClientSessionHandle session, CancellationToken cancellationToken) - { - var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); - - options = options ?? new DropCollectionOptions(); - - var encryptedFieldsMap = _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap; - if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options.EncryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields)) - { - if (encryptedFieldsMap != null) - { - var listCollectionOptions = new ListCollectionsOptions() { Filter = $"{{ name : '{collectionNamespace.CollectionName}' }}" }; - var currrentCollectionInfo = ListCollections(session, listCollectionOptions, cancellationToken).FirstOrDefault(); - effectiveEncryptedFields = currrentCollectionInfo - ?.GetValue("options", defaultValue: null) - ?.AsBsonDocument - ?.GetValue("encryptedFields", defaultValue: null) - ?.ToBsonDocument(); - } - } - - var messageEncoderSettings = GetMessageEncoderSettings(); - return DropCollectionOperation.CreateEncryptedDropCollectionOperationIfConfigured( - collectionNamespace, - effectiveEncryptedFields, - messageEncoderSettings, - (dco) => - { - dco.WriteConcern = _settings.WriteConcern; - }); - } - - private async Task> CreateDropCollectionOperationAsync(string name, DropCollectionOptions options, IClientSessionHandle session, CancellationToken cancellationToken) + private IWriteOperation CreateDropCollectionOperation(CollectionNamespace collectionNamespace, BsonDocument effectiveEncryptedFields) { - var collectionNamespace = new CollectionNamespace(_databaseNamespace, name); - - options = options ?? new DropCollectionOptions(); - - var encryptedFieldsMap = _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap; - if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options.EncryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields)) - { - if (encryptedFieldsMap != null) - { - var listCollectionOptions = new ListCollectionsOptions() { Filter = $"{{ name : '{collectionNamespace.CollectionName}' }}" }; - var currentCollectionsInfo = await ListCollectionsAsync(session, listCollectionOptions, cancellationToken).ConfigureAwait(false); - var currentCollectionInfo = await currentCollectionsInfo.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); - effectiveEncryptedFields = currentCollectionInfo - ?.GetValue("options", defaultValue: null) - ?.AsBsonDocument - ?.GetValue("encryptedFields", defaultValue: null) - ?.ToBsonDocument(); - } - } - var messageEncoderSettings = GetMessageEncoderSettings(); return DropCollectionOperation.CreateEncryptedDropCollectionOperationIfConfigured( collectionNamespace, @@ -778,9 +677,10 @@ private async Task> CreateDropCollectionOperationA }); } - private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollectionNamesOptions options, RenderArgs renderArgs) + private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollectionNamesOptions options) { var messageEncoderSettings = GetMessageEncoderSettings(); + var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); return new ListCollectionsOperation(_databaseNamespace, messageEncoderSettings) { AuthorizedCollections = options?.AuthorizedCollections, @@ -791,8 +691,9 @@ private ListCollectionsOperation CreateListCollectionNamesOperation(ListCollecti }; } - private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsOptions options, RenderArgs renderArgs) + private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsOptions options) { + var renderArgs = GetRenderArgs(BsonDocumentSerializer.Instance); var messageEncoderSettings = GetMessageEncoderSettings(); return new ListCollectionsOperation(_databaseNamespace, messageEncoderSettings) { @@ -803,23 +704,10 @@ private ListCollectionsOperation CreateListCollectionsOperation(ListCollectionsO }; } - private IReadBinding CreateReadBinding(IClientSessionHandle session, ReadPreference readPreference) - { - if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) - { - throw new InvalidOperationException("Read preference in a transaction must be primary."); - } - - return ChannelPinningHelper.CreateReadBinding(_cluster, session.WrappedCoreSession.Fork(), readPreference); - } - - private IWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session) - { - return ChannelPinningHelper.CreateReadWriteBinding(_cluster, session.WrappedCoreSession.Fork()); - } - private RenameCollectionOperation CreateRenameCollectionOperation(string oldName, string newName, RenameCollectionOptions options) { + options ??= new RenameCollectionOptions(); + var messageEncoderSettings = GetMessageEncoderSettings(); return new RenameCollectionOperation( new CollectionNamespace(_databaseNamespace, oldName), @@ -843,9 +731,10 @@ private ReadCommandOperation CreateRunCommandOperation(Command private ChangeStreamOperation CreateChangeStreamOperation( PipelineDefinition, TResult> pipeline, - ChangeStreamOptions options, - ExpressionTranslationOptions translationOptions) + ChangeStreamOptions options) { + var translationOptions = _client.Settings.TranslationOptions; + return ChangeStreamHelper.CreateChangeStreamOperation( this, pipeline, @@ -856,53 +745,68 @@ private ChangeStreamOperation CreateChangeStreamOperation( translationOptions); } - private IEnumerable ExtractCollectionNames(IEnumerable collections) - { - return collections.Select(collection => collection["name"].AsString); - } + private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => ExecuteReadOperation(session, operation, _readOperationOptions, cancellationToken); - private T ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) - { - var readPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperation(session, operation, readPreference, cancellationToken); - } + private TResult ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadOperationOptions options, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperation(session, operation, options, true, cancellationToken); - private T ExecuteReadOperation(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken) - { - using (var binding = CreateReadBinding(session, readPreference)) - { - return _operationExecutor.ExecuteReadOperation(binding, operation, cancellationToken); - } - } + private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) + => ExecuteReadOperationAsync(session, operation, _readOperationOptions, cancellationToken); - private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, CancellationToken cancellationToken) - { - var readPreference = ReadPreferenceResolver.GetEffectiveReadPreference(session, null, _settings.ReadPreference); - return ExecuteReadOperationAsync(session, operation, readPreference, cancellationToken); - } + private Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadOperationOptions options, CancellationToken cancellationToken) + => _operationExecutor.ExecuteReadOperationAsync(session, operation, options, true, cancellationToken); + + private TResult ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperation(session, operation, _writeOperationOptions, true, cancellationToken); - private async Task ExecuteReadOperationAsync(IClientSessionHandle session, IReadOperation operation, ReadPreference readPreference, CancellationToken cancellationToken) + private Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + => _operationExecutor.ExecuteWriteOperationAsync(session, operation, _writeOperationOptions, true, cancellationToken); + + private IEnumerable ExtractCollectionNames(IEnumerable collections) { - using (var binding = CreateReadBinding(session, readPreference)) - { - return await _operationExecutor.ExecuteReadOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); - } + return collections.Select(collection => collection["name"].AsString); } - private T ExecuteWriteOperation(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + private BsonDocument GetEffectiveEncryptedFields(IClientSessionHandle session, CollectionNamespace collectionNamespace, DropCollectionOptions options, CancellationToken cancellationToken) { - using (var binding = CreateReadWriteBinding(session)) + var encryptedFieldsMap = _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap; + if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options?.EncryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields)) { - return _operationExecutor.ExecuteWriteOperation(binding, operation, cancellationToken); + if (encryptedFieldsMap != null) + { + var listCollectionOptions = new ListCollectionsOptions() { Filter = $"{{ name : '{collectionNamespace.CollectionName}' }}" }; + var currentCollectionInfo = ListCollections(session, listCollectionOptions, cancellationToken: cancellationToken).FirstOrDefault(); + effectiveEncryptedFields = currentCollectionInfo + ?.GetValue("options", defaultValue: null) + ?.AsBsonDocument + ?.GetValue("encryptedFields", defaultValue: null) + ?.ToBsonDocument(); + } } + + return effectiveEncryptedFields; } - private async Task ExecuteWriteOperationAsync(IClientSessionHandle session, IWriteOperation operation, CancellationToken cancellationToken) + private async Task GetEffectiveEncryptedFieldsAsync(IClientSessionHandle session, CollectionNamespace collectionNamespace, DropCollectionOptions options, CancellationToken cancellationToken) { - using (var binding = CreateReadWriteBinding(session)) + var encryptedFieldsMap = _client.Settings?.AutoEncryptionOptions?.EncryptedFieldsMap; + if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, options?.EncryptedFields, encryptedFieldsMap, out var effectiveEncryptedFields)) { - return await _operationExecutor.ExecuteWriteOperationAsync(binding, operation, cancellationToken).ConfigureAwait(false); + if (encryptedFieldsMap != null) + { + var listCollectionOptions = new ListCollectionsOptions() { Filter = $"{{ name : '{collectionNamespace.CollectionName}' }}" }; + var currentCollectionsInfo = await ListCollectionsAsync(session, listCollectionOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + var currentCollectionInfo = await currentCollectionsInfo.FirstOrDefaultAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + effectiveEncryptedFields = currentCollectionInfo + ?.GetValue("options", defaultValue: null) + ?.AsBsonDocument + ?.GetValue("encryptedFields", defaultValue: null) + ?.ToBsonDocument(); + } } + + return effectiveEncryptedFields; } private MessageEncoderSettings GetMessageEncoderSettings() @@ -932,37 +836,5 @@ private RenderArgs GetRenderArgs(IBsonSerializer(documentSerializer, _settings.SerializerRegistry, translationOptions: translationOptions); } - - private void UsingImplicitSession(Action func, CancellationToken cancellationToken) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - func(session); - } - } - - private TResult UsingImplicitSession(Func func, CancellationToken cancellationToken) - { - using (var session = _operationExecutor.StartImplicitSession(cancellationToken)) - { - return func(session); - } - } - - private async Task UsingImplicitSessionAsync(Func funcAsync, CancellationToken cancellationToken) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - await funcAsync(session).ConfigureAwait(false); - } - } - - private async Task UsingImplicitSessionAsync(Func> funcAsync, CancellationToken cancellationToken) - { - using (var session = await _operationExecutor.StartImplicitSessionAsync(cancellationToken).ConfigureAwait(false)) - { - return await funcAsync(session).ConfigureAwait(false); - } - } } } diff --git a/src/MongoDB.Driver/OperationExecutor.cs b/src/MongoDB.Driver/OperationExecutor.cs index cc521cdf762..295d136bd09 100644 --- a/src/MongoDB.Driver/OperationExecutor.cs +++ b/src/MongoDB.Driver/OperationExecutor.cs @@ -13,50 +13,138 @@ * limitations under the License. */ +using System; using System.Threading; using System.Threading.Tasks; +using MongoDB.Driver.Core; using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; namespace MongoDB.Driver { internal sealed class OperationExecutor : IOperationExecutor { - private readonly MongoClient _client; + private readonly IMongoClient _client; + private bool _isDisposed; - public OperationExecutor(MongoClient client) + public OperationExecutor(IMongoClient client) { _client = client; } - public TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public void Dispose() { + _isDisposed = true; + } + + public TResult ExecuteReadOperation( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken) + { + Ensure.IsNotNull(operation, nameof(operation)); + Ensure.IsNotNull(options, nameof(options)); + Ensure.IsNotNull(session, nameof(session)); + ThrowIfDisposed(); + + var readPreference = options.GetEffectiveReadPreference(session); + using var binding = CreateReadBinding(session, readPreference, allowChannelPinning); return operation.Execute(binding, cancellationToken); } - public async Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public async Task ExecuteReadOperationAsync( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken) { + Ensure.IsNotNull(operation, nameof(operation)); + Ensure.IsNotNull(options, nameof(options)); + Ensure.IsNotNull(session, nameof(session)); + ThrowIfDisposed(); + + var readPreference = options.GetEffectiveReadPreference(session); + using var binding = CreateReadBinding(session, readPreference, allowChannelPinning); return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); } - public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public TResult ExecuteWriteOperation( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken) { + Ensure.IsNotNull(operation, nameof(operation)); + Ensure.IsNotNull(options, nameof(options)); + Ensure.IsNotNull(session, nameof(session)); + ThrowIfDisposed(); + + using var binding = CreateReadWriteBinding(session, allowChannelPinning); return operation.Execute(binding, cancellationToken); } - public async Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public async Task ExecuteWriteOperationAsync( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions options, + bool allowChannelPinning, + CancellationToken cancellationToken) { + Ensure.IsNotNull(operation, nameof(operation)); + Ensure.IsNotNull(options, nameof(options)); + Ensure.IsNotNull(session, nameof(session)); + ThrowIfDisposed(); + + using var binding = CreateReadWriteBinding(session, allowChannelPinning); return await operation.ExecuteAsync(binding, cancellationToken).ConfigureAwait(false); } - public IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken) + public IClientSessionHandle StartImplicitSession() + { + ThrowIfDisposed(); + var options = new ClientSessionOptions { CausalConsistency = false, Snapshot = false }; + var coreSession = _client.GetClusterInternal().StartSession(options.ToCore(isImplicit: true)); + return new ClientSessionHandle(_client, options, coreSession); + } + + private IReadBindingHandle CreateReadBinding(IClientSessionHandle session, ReadPreference readPreference, bool allowChannelPinning) + { + if (session.IsInTransaction && readPreference.ReadPreferenceMode != ReadPreferenceMode.Primary) + { + throw new InvalidOperationException("Read preference in a transaction must be primary."); + } + + if (allowChannelPinning) + { + return ChannelPinningHelper.CreateReadBinding(_client.GetClusterInternal(), session.WrappedCoreSession.Fork(), readPreference); + } + + var binding = new ReadPreferenceBinding(_client.GetClusterInternal(), readPreference, session.WrappedCoreSession.Fork()); + return new ReadBindingHandle(binding); + } + + private IReadWriteBindingHandle CreateReadWriteBinding(IClientSessionHandle session, bool allowChannelPinning) { - return _client.StartImplicitSession(cancellationToken); + if (allowChannelPinning) + { + return ChannelPinningHelper.CreateReadWriteBinding(_client.GetClusterInternal(), session.WrappedCoreSession.Fork()); + } + + var binding = new WritableServerBinding(_client.GetClusterInternal(), session.WrappedCoreSession.Fork()); + return new ReadWriteBindingHandle(binding); } - public Task StartImplicitSessionAsync(CancellationToken cancellationToken) + private void ThrowIfDisposed() { - return _client.StartImplicitSessionAsync(cancellationToken); + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(OperationExecutor)); + } } } } diff --git a/src/MongoDB.Driver/ReadOperationOptions.cs b/src/MongoDB.Driver/ReadOperationOptions.cs new file mode 100644 index 00000000000..2473d5dc045 --- /dev/null +++ b/src/MongoDB.Driver/ReadOperationOptions.cs @@ -0,0 +1,42 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace MongoDB.Driver +{ + internal record ReadOperationOptions(ReadPreference ExplicitReadPreference = null, ReadPreference DefaultReadPreference = null); + + internal static class ReadOperationOptionsExtensions + { + public static ReadPreference GetEffectiveReadPreference(this ReadOperationOptions options, IClientSessionHandle session) + { + if (options.ExplicitReadPreference != null) + { + return options.ExplicitReadPreference; + } + + if (session.IsInTransaction) + { + var transactionReadPreference = session.WrappedCoreSession.CurrentTransaction.TransactionOptions.ReadPreference; + if (transactionReadPreference != null) + { + return transactionReadPreference; + } + } + + return options.DefaultReadPreference ?? ReadPreference.Primary; + } + } +} + diff --git a/src/MongoDB.Driver/ReadPreferenceResolver.cs b/src/MongoDB.Driver/ReadPreferenceResolver.cs deleted file mode 100644 index 6fbc9ed27f8..00000000000 --- a/src/MongoDB.Driver/ReadPreferenceResolver.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2018-present MongoDB Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -namespace MongoDB.Driver -{ - internal static class ReadPreferenceResolver - { - public static ReadPreference GetEffectiveReadPreference( - IClientSessionHandle session, - ReadPreference explicitReadPreference, - ReadPreference defaultReadPreference) - { - if (explicitReadPreference != null) - { - return explicitReadPreference; - } - - if (session.IsInTransaction) - { - var transactionReadPreference = session.WrappedCoreSession.CurrentTransaction.TransactionOptions.ReadPreference; - if (transactionReadPreference != null) - { - return transactionReadPreference; - } - } - - return defaultReadPreference ?? ReadPreference.Primary; - } - } -} diff --git a/src/MongoDB.Driver/WriteOperationOptions.cs b/src/MongoDB.Driver/WriteOperationOptions.cs new file mode 100644 index 00000000000..fd56dc0fa78 --- /dev/null +++ b/src/MongoDB.Driver/WriteOperationOptions.cs @@ -0,0 +1,20 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace MongoDB.Driver +{ + internal record WriteOperationOptions(); +} + diff --git a/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs b/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs index 6c9c18b6410..6abf665a10e 100644 --- a/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs +++ b/tests/MongoDB.Driver.Tests/MockOperationExecutor.cs @@ -48,6 +48,10 @@ public int QueuedCallCount get { return _calls.Count; } } + public void Dispose() + { + } + public void EnqueueResult(TResult result) { _results.Enqueue(result); @@ -58,15 +62,20 @@ public void EnqueueException(Exception exception) _results.Enqueue(exception); } - public TResult ExecuteReadOperation(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public TResult ExecuteReadOperation( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions readOperationOptions, + bool allowChannelPinning, + CancellationToken cancellationToken) { _calls.Enqueue(new ReadCall { - Binding = binding, Operation = operation, CancellationToken = cancellationToken, - SessionId = binding.Session.Id, - UsedImplicitSession = binding.Session.IsImplicit + Options = readOperationOptions, + SessionId = session?.WrappedCoreSession.Id, + UsedImplicitSession = session == null || session.IsImplicit }); if (_results.Count > 0) @@ -85,11 +94,16 @@ public TResult ExecuteReadOperation(IReadBinding binding, IReadOperatio return default(TResult); } - public Task ExecuteReadOperationAsync(IReadBinding binding, IReadOperation operation, CancellationToken cancellationToken) + public Task ExecuteReadOperationAsync( + IClientSessionHandle session, + IReadOperation operation, + ReadOperationOptions readOperationOptions, + bool allowChannelPinning, + CancellationToken cancellationToken) { try { - var result = ExecuteReadOperation(binding, operation, cancellationToken); + var result = ExecuteReadOperation(session, operation, readOperationOptions, allowChannelPinning, cancellationToken); return Task.FromResult(result); } catch (Exception ex) @@ -100,15 +114,20 @@ public Task ExecuteReadOperationAsync(IReadBinding binding, IR } } - public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public TResult ExecuteWriteOperation( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions writeOperationOptions, + bool allowChannelPinning, + CancellationToken cancellationToken) { _calls.Enqueue(new WriteCall { - Binding = binding, Operation = operation, CancellationToken = cancellationToken, - SessionId = binding.Session.Id, - UsedImplicitSession = binding.Session.IsImplicit + Options = writeOperationOptions, + SessionId = session?.WrappedCoreSession.Id, + UsedImplicitSession = session == null || session.IsImplicit }); if (_results.Count > 0) @@ -127,11 +146,16 @@ public TResult ExecuteWriteOperation(IWriteBinding binding, IWriteOpera return default(TResult); } - public Task ExecuteWriteOperationAsync(IWriteBinding binding, IWriteOperation operation, CancellationToken cancellationToken) + public Task ExecuteWriteOperationAsync( + IClientSessionHandle session, + IWriteOperation operation, + WriteOperationOptions writeOperationOptions, + bool allowChannelPinning, + CancellationToken cancellationToken) { try { - var result = ExecuteWriteOperation(binding, operation, cancellationToken); + var result = ExecuteWriteOperation(session, operation, writeOperationOptions, allowChannelPinning, cancellationToken); return Task.FromResult(result); } catch (Exception ex) @@ -176,7 +200,7 @@ public WriteCall GetWriteCall() return writeCall; } - public IClientSessionHandle StartImplicitSession(CancellationToken cancellationToken) + public IClientSessionHandle StartImplicitSession() { var cluster = Mock.Of(); var options = new ClientSessionOptions(); @@ -186,25 +210,20 @@ public IClientSessionHandle StartImplicitSession(CancellationToken cancellationT return new ClientSessionHandle(_client, options, coreSessionHandle); } - public Task StartImplicitSessionAsync(CancellationToken cancellationToken) - { - return Task.FromResult(StartImplicitSession(cancellationToken)); - } - public class ReadCall { - public IReadBinding Binding { get; set; } public IReadOperation Operation { get; set; } public CancellationToken CancellationToken { get; set; } + public ReadOperationOptions Options { get; set; } public BsonDocument SessionId { get; set; } public bool UsedImplicitSession { get; set; } } public class WriteCall { - public IWriteBinding Binding { get; set; } public IWriteOperation Operation { get; set; } public CancellationToken CancellationToken { get; set; } + public WriteOperationOptions Options { get; set; } public BsonDocument SessionId { get; set; } public bool UsedImplicitSession { get; set; } } diff --git a/tests/MongoDB.Driver.Tests/MongoClientTests.cs b/tests/MongoDB.Driver.Tests/MongoClientTests.cs index 36d6bca9231..026f5b83cd4 100644 --- a/tests/MongoDB.Driver.Tests/MongoClientTests.cs +++ b/tests/MongoDB.Driver.Tests/MongoClientTests.cs @@ -119,9 +119,6 @@ public void Disposed_client_should_throw_on_member_access() var exception = Record.Exception(() => client.Cluster); exception.Should().BeOfType(); - - exception = Record.Exception(() => client.StartImplicitSession(default)); - exception.Should().BeOfType(); } [Theory] @@ -132,7 +129,7 @@ public void DropDatabase_should_invoke_the_correct_operation( { var operationExecutor = new MockOperationExecutor(); var writeConcern = new WriteConcern(1); - var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings()).WithWriteConcern(writeConcern); + var subject = new MongoClient(DriverTestConfiguration.GetClientSettings(), _ => operationExecutor).WithWriteConcern(writeConcern); var session = CreateClientSession(); using var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; @@ -183,7 +180,7 @@ public void ListDatabaseNames_should_invoke_the_correct_operation( [Values(false, true)] bool async) { var operationExecutor = new MockOperationExecutor(); - var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings()); + var subject = new MongoClient(DriverTestConfiguration.GetClientSettings(), _ => operationExecutor); var session = CreateClientSession(); using var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; @@ -277,7 +274,7 @@ public void ListDatabases_should_invoke_the_correct_operation( [Values(false, true)] bool async) { var operationExecutor = new MockOperationExecutor(); - var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings()); + var subject = new MongoClient(DriverTestConfiguration.GetClientSettings(), _ => operationExecutor); var session = CreateClientSession(); using var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; @@ -337,7 +334,7 @@ public void Watch_should_invoke_the_correct_operation( { var operationExecutor = new MockOperationExecutor(); var clientSettings = DriverTestConfiguration.GetClientSettings(); - var subject = new MongoClient(operationExecutor, clientSettings); + var subject = new MongoClient(clientSettings, _ => operationExecutor); var session = usingSession ? CreateClientSession() : null; var pipeline = new EmptyPipelineDefinition>().Limit(1); var options = new ChangeStreamOptions diff --git a/tests/MongoDB.Driver.Tests/MongoDatabasTests.cs b/tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs similarity index 98% rename from tests/MongoDB.Driver.Tests/MongoDatabasTests.cs rename to tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs index 68cb5526beb..c2fc8f9166b 100644 --- a/tests/MongoDB.Driver.Tests/MongoDatabasTests.cs +++ b/tests/MongoDB.Driver.Tests/MongoDatabaseTests.cs @@ -1097,8 +1097,8 @@ public void RunCommand_should_default_to_ReadPreference_primary( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1144,8 +1144,8 @@ public void RunCommand_should_use_the_provided_ReadPreference( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(readPreference); + call.Options.ExplicitReadPreference.Should().Be(readPreference); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1190,8 +1190,8 @@ public void RunCommand_should_run_a_non_read_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1236,8 +1236,8 @@ public void RunCommand_should_run_a_json_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); @@ -1282,8 +1282,8 @@ public void RunCommand_should_run_a_serialized_command( var call = _operationExecutor.GetReadCall(); VerifySessionAndCancellationToken(call, session, cancellationToken); - var binding = call.Binding.Should().BeOfType().Subject; - binding.ReadPreference.Should().Be(ReadPreference.Primary); + call.Options.ExplicitReadPreference.Should().BeNull(); + call.Options.DefaultReadPreference.Should().Be(ReadPreference.Primary); var op = call.Operation.Should().BeOfType>().Subject; op.DatabaseNamespace.Should().Be(_subject.DatabaseNamespace); diff --git a/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs b/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs new file mode 100644 index 00000000000..ef207d784f6 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/OperationExecutorTests.cs @@ -0,0 +1,156 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using MongoDB.Driver.Core.Bindings; +using MongoDB.Driver.Core.Clusters; +using MongoDB.Driver.Core.Operations; +using MongoDB.TestHelpers.XunitExtensions; +using Moq; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + public class OperationExecutorTests + { + [Fact] + public void StartImplicitSession_should_call_cluster_StartSession() + { + var subject = CreateSubject(out var clusterMock, out _); + + subject.StartImplicitSession(); + + clusterMock.Verify(c => c.StartSession(It.Is(v => v.IsImplicit && v.IsCausallyConsistent == false && v.IsSnapshot == false))); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteReadOperation_throws_on_null_operation([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var options = new ReadOperationOptions(); + var session = Mock.Of(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteReadOperationAsync(session, null, options, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteReadOperation(session, null, options, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("operation"); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteReadOperation_throws_on_null_options([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var operation = Mock.Of>(); + var session = Mock.Of(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteReadOperationAsync(session, operation, null, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteReadOperation(session, operation, null, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("options"); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteReadOperation_throws_on_null_session([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var operation = Mock.Of>(); + var options = new ReadOperationOptions(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteReadOperationAsync(null, operation, options, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteReadOperation(null, operation, options, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("session"); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteWriteOperation_throws_on_null_operation([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var options = new WriteOperationOptions(); + var session = Mock.Of(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteWriteOperationAsync(session, null, options, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteWriteOperation(session, null, options, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("operation"); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteWriteOperation_throws_on_null_options([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var operation = Mock.Of>(); + var session = Mock.Of(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteWriteOperationAsync(session, operation, null, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteWriteOperation(session, operation, null, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("options"); + } + + [Theory] + [ParameterAttributeData] + public async Task ExecuteWriteOperation_throws_on_null_session([Values(true, false)] bool async) + { + var subject = CreateSubject(out _, out _); + var operation = Mock.Of>(); + var options = new WriteOperationOptions(); + + var exception = async ? + await Record.ExceptionAsync(() => subject.ExecuteWriteOperationAsync(null, operation, options, true, CancellationToken.None)) : + Record.Exception(() => subject.ExecuteWriteOperation(null, operation, options, true, CancellationToken.None)); + + exception.Should().BeOfType() + .Subject.ParamName.Should().Be("session"); + } + + private OperationExecutor CreateSubject(out Mock clusterMock, out Mock implicitSessionMock) + { + implicitSessionMock = CreateCoreSessionMock(true); + clusterMock = new Mock(); + clusterMock.Setup(c => c.StartSession(It.IsAny())).Returns(implicitSessionMock.Object); + var clientMock = new Mock(); + clientMock.SetupGet(c => c.Cluster).Returns(clusterMock.Object); + return new OperationExecutor(clientMock.Object); + } + + private static Mock CreateCoreSessionMock(bool isImplicit) + { + var sessionMock = new Mock(); + sessionMock.SetupGet(s => s.IsImplicit).Returns(isImplicit); + sessionMock.Setup(s => s.Fork()).Returns(() => CreateCoreSessionMock(isImplicit).Object); + return sessionMock; + } + } +} + diff --git a/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs b/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs new file mode 100644 index 00000000000..585b2e442ff --- /dev/null +++ b/tests/MongoDB.Driver.Tests/ReadOperationOptionsTests.cs @@ -0,0 +1,70 @@ +/* Copyright 2010-present MongoDB Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System.Collections.Generic; +using FluentAssertions; +using MongoDB.Driver.Core.Bindings; +using Moq; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + public class ReadOperationOptionsTests + { + [Theory] + [MemberData(nameof(GetEffectiveReadPreferenceTestCases))] + public void GetEffectiveReadPreferenceTests( + ReadPreference expectedReadPreference, + ReadPreference explicitReadPreference, + ReadPreference defaultReadPreference, + IClientSessionHandle session) + { + var readOperationOptions = new ReadOperationOptions(explicitReadPreference, defaultReadPreference); + var result = readOperationOptions.GetEffectiveReadPreference(session); + + result.Should().Be(expectedReadPreference); + } + + public static IEnumerable GetEffectiveReadPreferenceTestCases() + { + var noTransactionSession = CreateSessionMock(null); + var inTransactionSession = CreateSessionMock(new TransactionOptions(readPreference: ReadPreference.Nearest)); + var inTransactionNoPreferenceSession = CreateSessionMock(new TransactionOptions()); + + yield return [ReadPreference.Primary, null, null, noTransactionSession]; + yield return [ReadPreference.Secondary, ReadPreference.Secondary, ReadPreference.SecondaryPreferred, noTransactionSession]; + yield return [ReadPreference.Secondary, ReadPreference.Secondary, ReadPreference.SecondaryPreferred, inTransactionSession]; + yield return [ReadPreference.SecondaryPreferred, null, ReadPreference.SecondaryPreferred, noTransactionSession]; + yield return [ReadPreference.Nearest, null, ReadPreference.SecondaryPreferred, inTransactionSession]; + yield return [ReadPreference.Primary, null, null, inTransactionNoPreferenceSession]; + yield return [ReadPreference.SecondaryPreferred, null, ReadPreference.SecondaryPreferred, inTransactionNoPreferenceSession]; + } + + private static IClientSessionHandle CreateSessionMock(TransactionOptions transactionOptions) + { + var sessionMock = new Mock(); + if (transactionOptions != null) + { + sessionMock.SetupGet(s => s.IsInTransaction).Returns(true); + var coreSessionMock = new Mock(); + coreSessionMock.SetupGet(s => s.CurrentTransaction).Returns(new CoreTransaction(0, transactionOptions)); + sessionMock.SetupGet(s => s.WrappedCoreSession).Returns(coreSessionMock.Object); + } + + return sessionMock.Object; + } + } +} +