Browse Source

Merge pull request #23007 from mosindi/feature/mongodb-client-factory

pull/23021/head
maliming 10 months ago
committed by GitHub
parent
commit
40a3fe91c7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 44
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/AbpMongoClientFactory.cs
  2. 13
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IAbpMongoClientFactory.cs
  3. 28
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs
  4. 79
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs

44
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<string, MongoClient> ClientCache { get; }
protected AbpMongoDbContextOptions Options { get; }
public AbpMongoClientFactory(IOptions<AbpMongoDbContextOptions> options)
{
Options = options.Value;
ClientCache = new ConcurrentDictionary<string, MongoClient>();
}
public virtual Task<MongoClient> 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);
});
}
}

13
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<MongoClient> GetAsync(MongoUrl mongoUrl);
[Obsolete("Use GetAsync method")]
MongoClient Get(MongoUrl mongoUrl);
}

28
framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs

@ -4,11 +4,11 @@ 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;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.Clients;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
@ -24,23 +24,23 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
protected readonly IConnectionStringResolver ConnectionStringResolver;
protected readonly ICancellationTokenProvider CancellationTokenProvider;
protected readonly ICurrentTenant CurrentTenant;
protected readonly AbpMongoDbContextOptions Options;
protected readonly IMongoDbContextTypeProvider DbContextTypeProvider;
protected readonly IAbpMongoClientFactory MongoClientFactory;
public UnitOfWorkMongoDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
IOptions<AbpMongoDbContextOptions> options,
IMongoDbContextTypeProvider dbContextTypeProvider)
IMongoDbContextTypeProvider dbContextTypeProvider,
IAbpMongoClientFactory mongoClientFactory)
{
UnitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
DbContextTypeProvider = dbContextTypeProvider;
Options = options.Value;
MongoClientFactory = mongoClientFactory;
Logger = NullLogger<UnitOfWorkMongoDbContextProvider<TMongoDbContext>>.Instance;
}
@ -77,12 +77,11 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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<TMongoDbContext> GetDbContextAsync(CancellationToken cancellationToken = default)
@ -121,14 +120,13 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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);
var client = MongoClientFactory.Get(mongoUrl);
var database = client.GetDatabase(databaseName);
if (unitOfWork.Options.IsTransactional)
@ -148,7 +146,7 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : 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)
@ -297,14 +295,6 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
return ConnectionStringResolver.Resolve(dbContextType);
}
protected virtual MongoClient CreateMongoClient(MongoUrl mongoUrl)
{
var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl);
Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings);
return new MongoClient(mongoClientSettings);
}
protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default)
{
return CancellationTokenProvider.FallbackToProvider(preferredValue);

79
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs

@ -0,0 +1,79 @@
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Xunit;
namespace Volo.Abp.MongoDB.Clients;
[Collection(MongoTestCollection.Name)]
public class MongoClient_Factory_Tests : MongoDbTestBase
{
private readonly IAbpMongoClientFactory _factory;
public MongoClient_Factory_Tests()
{
_factory = GetRequiredService<IAbpMongoClientFactory>();
}
[Fact]
public async Task Should_Return_Same_Instance_For_Same_ConnectionString()
{
// Arrange
var mongoUrl = new MongoUrl("mongodb://localhost:27017/my-db");
// Act
var client1 = await _factory.GetAsync(mongoUrl);
var client2 = await _factory.GetAsync(mongoUrl);
// Assert
Assert.Same(client1, client2);
}
[Fact]
public async Task Should_Return_Different_Instances_For_Different_ConnectionStrings()
{
// Arrange
var mongoUrl1 = new MongoUrl("mongodb://localhost:27017/db1");
var mongoUrl2 = new MongoUrl("mongodb://localhost:27017/db2");
// Act
var client1 = await _factory.GetAsync(mongoUrl1);
var client2 = await _factory.GetAsync(mongoUrl2);
// Assert
Assert.NotSame(client1, client2);
}
[Fact]
public async Task Should_Not_Throw_For_Valid_But_Unreachable_Connection()
{
// Arrange
var mongoUrl = new MongoUrl("mongodb://unreachablehost:12345/any");
// Act
var client = await _factory.GetAsync(mongoUrl);
// Assert
Assert.NotNull(client); // Even though it's not connectable now, the instance can be created
}
[Fact]
public async Task Should_Be_ThreadSafe_When_Accessed_Concurrently()
{
var mongoUrl = new MongoUrl("mongodb://localhost:27017/threadsafe");
var results = new MongoClient[100];
await Parallel.ForAsync(0, 100, async (i, _) =>
{
results[i] = await _factory.GetAsync(mongoUrl);
});
Assert.All(results, client => Assert.Same(results[0], client));
}
[Fact]
public async Task Should_Throw_If_ConnectionString_Is_Null_Or_Empty()
{
await Assert.ThrowsAsync<ArgumentNullException>(() => _factory.GetAsync(null!));
}
}
Loading…
Cancel
Save