From a4b32d41905b006bb05970be354e3852f4bd04d3 Mon Sep 17 00:00:00 2001 From: Mustafa Ozan Sindi Date: Wed, 4 Jun 2025 13:01:16 +0300 Subject: [PATCH 1/2] feat(mongodb): Add MongoClientFactory to reuse MongoClient per connection string" --- .../Volo/Abp/MongoDB/AbpMongoDbModule.cs | 5 +- .../MongoDB/Clients/IMongoClientFactory.cs | 9 ++ .../Abp/MongoDB/Clients/MongoClientFactory.cs | 32 ++++++++ .../UnitOfWorkMongoDbContextProvider.cs | 18 ++-- .../Clients/MongoClient_Factory_Tests.cs | 82 +++++++++++++++++++ 5 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs create mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs create mode 100644 framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index 195491d989..61e1e2120d 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -8,6 +8,7 @@ using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; +using Volo.Abp.MongoDB.Clients; using Volo.Abp.MongoDB.DependencyInjection; using Volo.Abp.Uow.MongoDB; using Volo.Abp.MongoDB.DistributedEvents; @@ -30,6 +31,8 @@ public class AbpMongoDbModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { + context.Services.AddSingleton(); + context.Services.TryAddTransient( typeof(IMongoDbContextProvider<>), typeof(UnitOfWorkMongoDbContextProvider<>) @@ -61,4 +64,4 @@ public class AbpMongoDbModule : AbpModule options.IgnoredEventSelectors.Add(); }); } -} +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs new file mode 100644 index 0000000000..aa63574e74 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs @@ -0,0 +1,9 @@ +using MongoDB.Driver; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MongoDB.Clients; + +public interface IMongoClientFactory : ISingletonDependency +{ + MongoClient GetClient(string connectionString); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs new file mode 100644 index 0000000000..80f866fda7 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Options; +using MongoDB.Driver; + +namespace Volo.Abp.MongoDB.Clients; + +public class MongoClientFactory : IMongoClientFactory +{ + private readonly ConcurrentDictionary _clients = new(); + private readonly AbpMongoDbContextOptions Options; + + public MongoClientFactory(IOptions options) + { + Options = options.Value; + } + + public MongoClient GetClient(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + { + throw new ArgumentException("Connection string must not be null or empty.", nameof(connectionString)); + } + + return _clients.GetOrAdd(connectionString, cs => + { + var mongoClientSettings = MongoClientSettings.FromUrl(new MongoUrl(cs)); + Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings); + return new MongoClient(mongoClientSettings); + }); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index 1585bc1332..bf305abefa 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -9,6 +9,7 @@ using MongoDB.Bson; using MongoDB.Driver; using Volo.Abp.Data; using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.Clients; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -26,6 +27,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext protected readonly ICurrentTenant CurrentTenant; protected readonly AbpMongoDbContextOptions Options; protected readonly IMongoDbContextTypeProvider DbContextTypeProvider; + protected readonly IMongoClientFactory MongoClientFactory; public UnitOfWorkMongoDbContextProvider( IUnitOfWorkManager unitOfWorkManager, @@ -33,13 +35,14 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext ICancellationTokenProvider cancellationTokenProvider, ICurrentTenant currentTenant, IOptions options, - IMongoDbContextTypeProvider dbContextTypeProvider) + IMongoDbContextTypeProvider dbContextTypeProvider, IMongoClientFactory mongoClientFactory) { UnitOfWorkManager = unitOfWorkManager; ConnectionStringResolver = connectionStringResolver; CancellationTokenProvider = cancellationTokenProvider; CurrentTenant = currentTenant; DbContextTypeProvider = dbContextTypeProvider; + MongoClientFactory = mongoClientFactory; Options = options.Value; Logger = NullLogger>.Instance; @@ -77,12 +80,11 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext databaseName = ConnectionStringNameAttribute.GetConnStringName(targetDbContextType); } - //TODO: Create only single MongoDbClient per connection string in an application (extract MongoClientCache for example). var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, () => new MongoDbDatabaseApi(CreateDbContext(unitOfWork, mongoUrl, databaseName))); - return (TMongoDbContext)((MongoDbDatabaseApi) databaseApi).DbContext; + return (TMongoDbContext)((MongoDbDatabaseApi)databaseApi).DbContext; } public virtual async Task GetDbContextAsync(CancellationToken cancellationToken = default) @@ -121,11 +123,10 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext unitOfWork.AddDatabaseApi(dbContextKey, databaseApi); } - return (TMongoDbContext)((MongoDbDatabaseApi) databaseApi).DbContext; + return (TMongoDbContext)((MongoDbDatabaseApi)databaseApi).DbContext; } [Obsolete("Use CreateDbContextAsync")] - private TMongoDbContext CreateDbContext(IUnitOfWork unitOfWork, MongoUrl mongoUrl, string databaseName) { var client = CreateMongoClient(mongoUrl); @@ -299,14 +300,11 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext protected virtual MongoClient CreateMongoClient(MongoUrl mongoUrl) { - var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl); - Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings); - - return new MongoClient(mongoClientSettings); + return MongoClientFactory.GetClient(mongoUrl.ToString()); } protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default) { return CancellationTokenProvider.FallbackToProvider(preferredValue); } -} +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs new file mode 100644 index 0000000000..b2f8c9b7d5 --- /dev/null +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading.Tasks; +using MongoDB.Driver; +using Volo.Abp.TestApp.Testing; +using Xunit; + +namespace Volo.Abp.MongoDB.Clients; + +[Collection(MongoTestCollection.Name)] +public class MongoClient_Factory_Tests : MongoDbTestBase +{ + private readonly IMongoClientFactory _factory; + + public MongoClient_Factory_Tests() + { + _factory = GetRequiredService(); + } + + [Fact] + public void Should_Return_Same_Instance_For_Same_ConnectionString() + { + // Arrange + var connectionString = "mongodb://localhost:27017/my-db"; + + // Act + var client1 = _factory.GetClient(connectionString); + var client2 = _factory.GetClient(connectionString); + + // Assert + Assert.Same(client1, client2); + } + + [Fact] + public void Should_Return_Different_Instances_For_Different_ConnectionStrings() + { + // Arrange + var cs1 = "mongodb://localhost:27017/db1"; + var cs2 = "mongodb://localhost:27017/db2"; + + // Act + var client1 = _factory.GetClient(cs1); + var client2 = _factory.GetClient(cs2); + + // Assert + Assert.NotSame(client1, client2); + } + + [Fact] + public void Should_Not_Throw_For_Valid_But_Unreachable_Connection() + { + // Arrange + var cs = "mongodb://unreachablehost:12345/any"; + + // Act + var client = _factory.GetClient(cs); + + // Assert + Assert.NotNull(client); // Even though it's not connectable now, the instance can be created + } + + [Fact] + public void Should_Be_ThreadSafe_When_Accessed_Concurrently() + { + var connectionString = "mongodb://localhost:27017/threadsafe"; + MongoClient[] results = new MongoClient[100]; + + Parallel.For(0, 100, i => + { + results[i] = _factory.GetClient(connectionString); + }); + + Assert.All(results, client => Assert.Same(results[0], client)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void Should_Throw_If_ConnectionString_Is_Null_Or_Empty(string connectionString) + { + Assert.Throws(() => _factory.GetClient(connectionString)); + } +} \ No newline at end of file From 02436d8c70ffc0fbfd37c31f08f3edc92f7b3565 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 4 Jun 2025 21:28:20 +0800 Subject: [PATCH 2/2] Refactor `AbpMongoClientFactory`. --- .../Volo/Abp/MongoDB/AbpMongoDbModule.cs | 5 +- .../MongoDB/Clients/AbpMongoClientFactory.cs | 44 ++++++++++++++++ .../MongoDB/Clients/IAbpMongoClientFactory.cs | 13 +++++ .../MongoDB/Clients/IMongoClientFactory.cs | 9 ---- .../Abp/MongoDB/Clients/MongoClientFactory.cs | 32 ------------ .../UnitOfWorkMongoDbContextProvider.cs | 20 +++----- .../Clients/MongoClient_Factory_Tests.cs | 51 +++++++++---------- 7 files changed, 88 insertions(+), 86 deletions(-) create mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/AbpMongoClientFactory.cs create mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IAbpMongoClientFactory.cs delete mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs delete mode 100644 framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index 61e1e2120d..195491d989 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -8,7 +8,6 @@ using Volo.Abp.Domain; using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; -using Volo.Abp.MongoDB.Clients; using Volo.Abp.MongoDB.DependencyInjection; using Volo.Abp.Uow.MongoDB; using Volo.Abp.MongoDB.DistributedEvents; @@ -31,8 +30,6 @@ public class AbpMongoDbModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddSingleton(); - context.Services.TryAddTransient( typeof(IMongoDbContextProvider<>), typeof(UnitOfWorkMongoDbContextProvider<>) @@ -64,4 +61,4 @@ public class AbpMongoDbModule : AbpModule options.IgnoredEventSelectors.Add(); }); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/AbpMongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/AbpMongoClientFactory.cs new file mode 100644 index 0000000000..983f7e067e --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/AbpMongoClientFactory.cs @@ -0,0 +1,44 @@ +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MongoDB.Clients; + +public class AbpMongoClientFactory : IAbpMongoClientFactory, ISingletonDependency +{ + protected ConcurrentDictionary ClientCache { get; } + protected AbpMongoDbContextOptions Options { get; } + + public AbpMongoClientFactory(IOptions options) + { + Options = options.Value; + ClientCache = new ConcurrentDictionary(); + } + + public virtual Task GetAsync(MongoUrl mongoUrl) + { + Check.NotNull(mongoUrl, nameof(mongoUrl)); + + return Task.FromResult( + ClientCache.GetOrAdd(mongoUrl.ToString(), _ => + { + var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl); + Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings); + return new MongoClient(mongoClientSettings); + })); + } + + public virtual MongoClient Get(MongoUrl mongoUrl) + { + Check.NotNull(mongoUrl, nameof(mongoUrl)); + + return ClientCache.GetOrAdd(mongoUrl.ToString(), _ => + { + var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl); + Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings); + return new MongoClient(mongoClientSettings); + }); + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IAbpMongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IAbpMongoClientFactory.cs new file mode 100644 index 0000000000..bf0d20d3ae --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IAbpMongoClientFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using MongoDB.Driver; + +namespace Volo.Abp.MongoDB.Clients; + +public interface IAbpMongoClientFactory +{ + Task GetAsync(MongoUrl mongoUrl); + + [Obsolete("Use GetAsync method")] + MongoClient Get(MongoUrl mongoUrl); +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs deleted file mode 100644 index aa63574e74..0000000000 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MongoDB.Driver; -using Volo.Abp.DependencyInjection; - -namespace Volo.Abp.MongoDB.Clients; - -public interface IMongoClientFactory : ISingletonDependency -{ - MongoClient GetClient(string connectionString); -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs deleted file mode 100644 index 80f866fda7..0000000000 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Concurrent; -using Microsoft.Extensions.Options; -using MongoDB.Driver; - -namespace Volo.Abp.MongoDB.Clients; - -public class MongoClientFactory : IMongoClientFactory -{ - private readonly ConcurrentDictionary _clients = new(); - private readonly AbpMongoDbContextOptions Options; - - public MongoClientFactory(IOptions options) - { - Options = options.Value; - } - - public MongoClient GetClient(string connectionString) - { - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ArgumentException("Connection string must not be null or empty.", nameof(connectionString)); - } - - return _clients.GetOrAdd(connectionString, cs => - { - var mongoClientSettings = MongoClientSettings.FromUrl(new MongoUrl(cs)); - Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings); - return new MongoClient(mongoClientSettings); - }); - } -} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs index bf305abefa..acea595dce 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Volo.Abp.Data; @@ -25,17 +24,16 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext protected readonly IConnectionStringResolver ConnectionStringResolver; protected readonly ICancellationTokenProvider CancellationTokenProvider; protected readonly ICurrentTenant CurrentTenant; - protected readonly AbpMongoDbContextOptions Options; protected readonly IMongoDbContextTypeProvider DbContextTypeProvider; - protected readonly IMongoClientFactory MongoClientFactory; + protected readonly IAbpMongoClientFactory MongoClientFactory; public UnitOfWorkMongoDbContextProvider( IUnitOfWorkManager unitOfWorkManager, IConnectionStringResolver connectionStringResolver, ICancellationTokenProvider cancellationTokenProvider, ICurrentTenant currentTenant, - IOptions options, - IMongoDbContextTypeProvider dbContextTypeProvider, IMongoClientFactory mongoClientFactory) + IMongoDbContextTypeProvider dbContextTypeProvider, + IAbpMongoClientFactory mongoClientFactory) { UnitOfWorkManager = unitOfWorkManager; ConnectionStringResolver = connectionStringResolver; @@ -43,7 +41,6 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext CurrentTenant = currentTenant; DbContextTypeProvider = dbContextTypeProvider; MongoClientFactory = mongoClientFactory; - Options = options.Value; Logger = NullLogger>.Instance; } @@ -129,7 +126,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext [Obsolete("Use CreateDbContextAsync")] private TMongoDbContext CreateDbContext(IUnitOfWork unitOfWork, MongoUrl mongoUrl, string databaseName) { - var client = CreateMongoClient(mongoUrl); + var client = MongoClientFactory.Get(mongoUrl); var database = client.GetDatabase(databaseName); if (unitOfWork.Options.IsTransactional) @@ -149,7 +146,7 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext string databaseName, CancellationToken cancellationToken = default) { - var client = CreateMongoClient(mongoUrl); + var client = await MongoClientFactory.GetAsync(mongoUrl); var database = client.GetDatabase(databaseName); if (unitOfWork.Options.IsTransactional) @@ -298,13 +295,8 @@ public class UnitOfWorkMongoDbContextProvider : IMongoDbContext return ConnectionStringResolver.Resolve(dbContextType); } - protected virtual MongoClient CreateMongoClient(MongoUrl mongoUrl) - { - return MongoClientFactory.GetClient(mongoUrl.ToString()); - } - protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default) { return CancellationTokenProvider.FallbackToProvider(preferredValue); } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs index b2f8c9b7d5..df649132fa 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using MongoDB.Driver; -using Volo.Abp.TestApp.Testing; using Xunit; namespace Volo.Abp.MongoDB.Clients; @@ -9,74 +8,72 @@ namespace Volo.Abp.MongoDB.Clients; [Collection(MongoTestCollection.Name)] public class MongoClient_Factory_Tests : MongoDbTestBase { - private readonly IMongoClientFactory _factory; + private readonly IAbpMongoClientFactory _factory; public MongoClient_Factory_Tests() { - _factory = GetRequiredService(); + _factory = GetRequiredService(); } [Fact] - public void Should_Return_Same_Instance_For_Same_ConnectionString() + public async Task Should_Return_Same_Instance_For_Same_ConnectionString() { // Arrange - var connectionString = "mongodb://localhost:27017/my-db"; + var mongoUrl = new MongoUrl("mongodb://localhost:27017/my-db"); // Act - var client1 = _factory.GetClient(connectionString); - var client2 = _factory.GetClient(connectionString); + var client1 = await _factory.GetAsync(mongoUrl); + var client2 = await _factory.GetAsync(mongoUrl); // Assert Assert.Same(client1, client2); } [Fact] - public void Should_Return_Different_Instances_For_Different_ConnectionStrings() + public async Task Should_Return_Different_Instances_For_Different_ConnectionStrings() { // Arrange - var cs1 = "mongodb://localhost:27017/db1"; - var cs2 = "mongodb://localhost:27017/db2"; + var mongoUrl1 = new MongoUrl("mongodb://localhost:27017/db1"); + var mongoUrl2 = new MongoUrl("mongodb://localhost:27017/db2"); // Act - var client1 = _factory.GetClient(cs1); - var client2 = _factory.GetClient(cs2); + var client1 = await _factory.GetAsync(mongoUrl1); + var client2 = await _factory.GetAsync(mongoUrl2); // Assert Assert.NotSame(client1, client2); } [Fact] - public void Should_Not_Throw_For_Valid_But_Unreachable_Connection() + public async Task Should_Not_Throw_For_Valid_But_Unreachable_Connection() { // Arrange - var cs = "mongodb://unreachablehost:12345/any"; + var mongoUrl = new MongoUrl("mongodb://unreachablehost:12345/any"); // Act - var client = _factory.GetClient(cs); + var client = await _factory.GetAsync(mongoUrl); // Assert Assert.NotNull(client); // Even though it's not connectable now, the instance can be created } [Fact] - public void Should_Be_ThreadSafe_When_Accessed_Concurrently() + public async Task Should_Be_ThreadSafe_When_Accessed_Concurrently() { - var connectionString = "mongodb://localhost:27017/threadsafe"; - MongoClient[] results = new MongoClient[100]; + var mongoUrl = new MongoUrl("mongodb://localhost:27017/threadsafe"); + var results = new MongoClient[100]; - Parallel.For(0, 100, i => + await Parallel.ForAsync(0, 100, async (i, _) => { - results[i] = _factory.GetClient(connectionString); + results[i] = await _factory.GetAsync(mongoUrl); }); Assert.All(results, client => Assert.Same(results[0], client)); } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void Should_Throw_If_ConnectionString_Is_Null_Or_Empty(string connectionString) + + [Fact] + public async Task Should_Throw_If_ConnectionString_Is_Null_Or_Empty() { - Assert.Throws(() => _factory.GetClient(connectionString)); + await Assert.ThrowsAsync(() => _factory.GetAsync(null!)); } -} \ No newline at end of file +}