Make multi-tenancy connection string resolving async.

pull/6809/head
Halil İbrahim Kalkan 5 years ago
parent 95965980e7
commit db6d5b50aa

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
@ -14,7 +15,18 @@ namespace Volo.Abp.Data
Options = options.Value;
}
[Obsolete("Use ResolveAsync method.")]
public virtual string Resolve(string connectionStringName = null)
{
return ResolveInternal(connectionStringName);
}
public virtual Task<string> ResolveAsync(string connectionStringName = null)
{
return Task.FromResult(ResolveInternal(connectionStringName));
}
private string ResolveInternal(string connectionStringName)
{
//Get module specific value if provided
if (!connectionStringName.IsNullOrEmpty())
@ -25,9 +37,9 @@ namespace Volo.Abp.Data
return moduleConnString;
}
}
//Get default value
return Options.ConnectionStrings.Default;
}
}
}
}

@ -1,10 +1,16 @@
using JetBrains.Annotations;
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Data
{
public interface IConnectionStringResolver
{
[NotNull]
[Obsolete("Use ResolveAsync method.")]
string Resolve(string connectionStringName = null);
[NotNull]
Task<string> ResolveAsync(string connectionStringName = null);
}
}

@ -1,10 +1,22 @@
namespace Volo.Abp.Data
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Data
{
public static class ConnectionStringResolverExtensions
{
[NotNull]
[Obsolete("Use ResolveAsync method")]
public static string Resolve<T>(this IConnectionStringResolver resolver)
{
return resolver.Resolve(ConnectionStringNameAttribute.GetConnStringName<T>());
}
[NotNull]
public static Task<string> ResolveAsync<T>(this IConnectionStringResolver resolver)
{
return resolver.ResolveAsync(ConnectionStringNameAttribute.GetConnStringName<T>());
}
}
}

@ -86,7 +86,11 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
}
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
//Use DefaultConnectionStringResolver.Resolve when we remove IConnectionStringResolver.Resolve
#pragma warning disable 618
var connectionString = serviceProvider.GetRequiredService<IConnectionStringResolver>().Resolve(connectionStringName);
#pragma warning restore 618
return new DbContextCreationContext(
connectionStringName,

@ -71,7 +71,7 @@ namespace Volo.Abp.Uow.EntityFrameworkCore
}
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
var connectionString = _connectionStringResolver.Resolve(connectionStringName);
var connectionString = await _connectionStringResolver.ResolveAsync(connectionStringName);
var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Domain.Repositories.MemoryDb
@ -6,9 +7,15 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
public interface IMemoryDbRepository<TEntity> : IRepository<TEntity>
where TEntity : class, IEntity
{
[Obsolete("Use GetDatabaseAsync() method.")]
IMemoryDatabase Database { get; }
[Obsolete("Use GetCollectionAsync() method.")]
IMemoryDatabaseCollection<TEntity> Collection { get; }
Task<IMemoryDatabase> GetDatabaseAsync();
Task<IMemoryDatabaseCollection<TEntity>> GetCollectionAsync();
}
public interface IMemoryDbRepository<TEntity, TKey> : IMemoryDbRepository<TEntity>, IRepository<TEntity, TKey>

@ -21,10 +21,22 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
//TODO: Add dbcontext just like mongodb implementation!
[Obsolete("Use GetCollectionAsync method.")]
public virtual IMemoryDatabaseCollection<TEntity> Collection => Database.Collection<TEntity>();
public async Task<IMemoryDatabaseCollection<TEntity>> GetCollectionAsync()
{
return (await GetDatabaseAsync()).Collection<TEntity>();
}
[Obsolete("Use GetDatabaseAsync method.")]
public virtual IMemoryDatabase Database => DatabaseProvider.GetDatabase();
public Task<IMemoryDatabase> GetDatabaseAsync()
{
return DatabaseProvider.GetDatabaseAsync();
}
protected IMemoryDatabaseProvider<TMemoryDbContext> DatabaseProvider { get; }
public ILocalEventBus LocalEventBus { get; set; }
@ -52,9 +64,9 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
return ApplyDataFilters(Collection.AsQueryable());
}
public override Task<IQueryable<TEntity>> GetQueryableAsync()
public override async Task<IQueryable<TEntity>> GetQueryableAsync()
{
return Task.FromResult(ApplyDataFilters(Collection.AsQueryable()));
return ApplyDataFilters((await GetCollectionAsync()).AsQueryable());
}
protected virtual async Task TriggerDomainEventsAsync(object entity)
@ -196,7 +208,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
await ApplyAbpConceptsForAddedEntityAsync(entity);
Collection.Add(entity);
(await GetCollectionAsync()).Add(entity);
return entity;
}
@ -220,7 +232,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
await TriggerDomainEventsAsync(entity);
Collection.Update(entity);
(await GetCollectionAsync()).Update(entity);
return entity;
}
@ -235,11 +247,11 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
if (entity is ISoftDelete softDeleteEntity && !IsHardDeleted(entity))
{
softDeleteEntity.IsDeleted = true;
Collection.Update(entity);
(await GetCollectionAsync()).Update(entity);
}
else
{
Collection.Remove(entity);
(await GetCollectionAsync()).Remove(entity);
}
}
@ -276,13 +288,13 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
}
public override Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
{
SetIdIfNeeded(entity);
return base.InsertAsync(entity, autoSave, cancellationToken);
await SetIdIfNeededAsync(entity);
return await base.InsertAsync(entity, autoSave, cancellationToken);
}
protected virtual void SetIdIfNeeded(TEntity entity)
protected virtual async Task SetIdIfNeededAsync(TEntity entity)
{
if (typeof(TKey) == typeof(int) ||
typeof(TKey) == typeof(long) ||
@ -290,7 +302,8 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb
{
if (EntityHelper.HasDefaultId(entity))
{
EntityHelper.TrySetId(entity, () => Database.GenerateNextId<TEntity, TKey>());
var nextId = (await GetDatabaseAsync()).GenerateNextId<TEntity, TKey>();
EntityHelper.TrySetId(entity, () => nextId);
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.MemoryDb;
@ -7,18 +8,32 @@ namespace Volo.Abp.Domain.Repositories
{
public static class MemoryDbCoreRepositoryExtensions
{
[Obsolete("Use GetDatabaseAsync method.")]
public static IMemoryDatabase GetDatabase<TEntity, TKey>(this IBasicRepository<TEntity, TKey> repository)
where TEntity : class, IEntity<TKey>
{
return repository.ToMemoryDbRepository().Database;
}
public static Task<IMemoryDatabase> GetDatabaseAsync<TEntity, TKey>(this IBasicRepository<TEntity, TKey> repository)
where TEntity : class, IEntity<TKey>
{
return repository.ToMemoryDbRepository().GetDatabaseAsync();
}
[Obsolete("Use GetCollectionAsync method.")]
public static IMemoryDatabaseCollection<TEntity> GetCollection<TEntity, TKey>(this IBasicRepository<TEntity, TKey> repository)
where TEntity : class, IEntity<TKey>
{
return repository.ToMemoryDbRepository().Collection;
}
public static Task<IMemoryDatabaseCollection<TEntity>> GetCollectionAsync<TEntity, TKey>(this IBasicRepository<TEntity, TKey> repository)
where TEntity : class, IEntity<TKey>
{
return repository.ToMemoryDbRepository().GetCollectionAsync();
}
public static IMemoryDbRepository<TEntity, TKey> ToMemoryDbRepository<TEntity, TKey>(this IBasicRepository<TEntity, TKey> repository)
where TEntity : class, IEntity<TKey>
{
@ -31,4 +46,4 @@ namespace Volo.Abp.Domain.Repositories
return memoryDbRepository;
}
}
}
}

@ -1,12 +1,20 @@
using Volo.Abp.Domain.Repositories.MemoryDb;
using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.MemoryDb;
namespace Volo.Abp.MemoryDb
{
public interface IMemoryDatabaseProvider<TMemoryDbContext>
where TMemoryDbContext : MemoryDbContext
{
[Obsolete("Use GetDbContextAsync method.")]
TMemoryDbContext DbContext { get; }
Task<TMemoryDbContext> GetDbContextAsync();
[Obsolete("Use GetDatabaseAsync method.")]
IMemoryDatabase GetDatabase();
Task<IMemoryDatabase> GetDatabaseAsync();
}
}
}

@ -1,4 +1,6 @@
using Volo.Abp.Data;
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories.MemoryDb;
using Volo.Abp.MemoryDb;
@ -8,7 +10,7 @@ namespace Volo.Abp.Uow.MemoryDb
where TMemoryDbContext : MemoryDbContext
{
public TMemoryDbContext DbContext { get; }
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IConnectionStringResolver _connectionStringResolver;
private readonly MemoryDatabaseManager _memoryDatabaseManager;
@ -16,7 +18,7 @@ namespace Volo.Abp.Uow.MemoryDb
public UnitOfWorkMemoryDatabaseProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
TMemoryDbContext dbContext,
TMemoryDbContext dbContext,
MemoryDatabaseManager memoryDatabaseManager)
{
_unitOfWorkManager = unitOfWorkManager;
@ -25,6 +27,12 @@ namespace Volo.Abp.Uow.MemoryDb
_memoryDatabaseManager = memoryDatabaseManager;
}
public Task<TMemoryDbContext> GetDbContextAsync()
{
return Task.FromResult(DbContext);
}
[Obsolete("Use GetDatabaseAsync method.")]
public IMemoryDatabase GetDatabase()
{
var unitOfWork = _unitOfWorkManager.Current;
@ -44,5 +52,25 @@ namespace Volo.Abp.Uow.MemoryDb
return ((MemoryDbDatabaseApi)databaseApi).Database;
}
public async Task<IMemoryDatabase> GetDatabaseAsync()
{
var unitOfWork = _unitOfWorkManager.Current;
if (unitOfWork == null)
{
throw new AbpException($"A {nameof(IMemoryDatabase)} instance can only be created inside a unit of work!");
}
var connectionString = await _connectionStringResolver.ResolveAsync<TMemoryDbContext>();
var dbContextKey = $"{typeof(TMemoryDbContext).FullName}_{connectionString}";
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
() => new MemoryDbDatabaseApi(
_memoryDatabaseManager.Get(connectionString)
));
return ((MemoryDbDatabaseApi)databaseApi).Database;
}
}
}
}

@ -64,7 +64,7 @@ namespace Volo.Abp.Uow.MongoDB
$"A {nameof(IMongoDatabase)} instance can only be created inside a unit of work!");
}
var connectionString = _connectionStringResolver.Resolve<TMongoDbContext>();
var connectionString = await _connectionStringResolver.ResolveAsync<TMongoDbContext>();
var dbContextKey = $"{typeof(TMongoDbContext).FullName}_{connectionString}";
var mongoUrl = new MongoUrl(connectionString);

@ -9,8 +9,10 @@ namespace Volo.Abp.MultiTenancy
Task<TenantConfiguration> FindAsync(Guid id);
[Obsolete("Use FindAsync method.")]
TenantConfiguration Find(string name);
[Obsolete("Use FindAsync method.")]
TenantConfiguration Find(Guid id);
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
@ -23,6 +24,58 @@ namespace Volo.Abp.MultiTenancy
_serviceProvider = serviceProvider;
}
public override async Task<string> ResolveAsync(string connectionStringName = null)
{
//No current tenant, fallback to default logic
if (_currentTenant.Id == null)
{
return await base.ResolveAsync(connectionStringName);
}
using (var serviceScope = _serviceProvider.CreateScope())
{
var tenantStore = serviceScope
.ServiceProvider
.GetRequiredService<ITenantStore>();
var tenant = await tenantStore.FindAsync(_currentTenant.Id.Value);
if (tenant?.ConnectionStrings == null)
{
return await base.ResolveAsync(connectionStringName);
}
//Requesting default connection string
if (connectionStringName == null)
{
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
//Requesting specific connection string
var connString = tenant.ConnectionStrings.GetOrDefault(connectionStringName);
if (connString != null)
{
return connString;
}
/* Requested a specific connection string, but it's not specified for the tenant.
* - If it's specified in options, use it.
* - If not, use tenant's default conn string.
*/
var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName);
if (connStringInOptions != null)
{
return connStringInOptions;
}
return tenant.ConnectionStrings.Default ??
Options.ConnectionStrings.Default;
}
}
[Obsolete("Use ResolveAsync method.")]
public override string Resolve(string connectionStringName = null)
{
//No current tenant, fallback to default logic

@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
@ -21,21 +22,21 @@ namespace Volo.Abp.Data
}
[Fact]
public void Should_Get_Default_ConnString_By_Default()
public async Task Should_Get_Default_ConnString_By_Default()
{
_connectionStringResolver.Resolve().ShouldBe(DefaultConnString);
(await _connectionStringResolver.ResolveAsync()).ShouldBe(DefaultConnString);
}
[Fact]
public void Should_Get_Specific_ConnString_IfDefined()
public async Task Should_Get_Specific_ConnString_IfDefined()
{
_connectionStringResolver.Resolve(Database1Name).ShouldBe(Database1ConnString);
(await _connectionStringResolver.ResolveAsync(Database1Name)).ShouldBe(Database1ConnString);
}
[Fact]
public void Should_Get_Default_ConnString_If_Not_Specified()
public async Task Should_Get_Default_ConnString_If_Not_Specified()
{
_connectionStringResolver.Resolve(Database2Name).ShouldBe(DefaultConnString);
(await _connectionStringResolver.ResolveAsync(Database2Name)).ShouldBe(DefaultConnString);
}
[DependsOn(typeof(AbpDataModule))]

@ -10,21 +10,21 @@ namespace Volo.Abp.TestApp.MemoryDb
{
public class CityRepository : MemoryDbRepository<TestAppMemoryDbContext, City, Guid>, ICityRepository
{
public CityRepository(IMemoryDatabaseProvider<TestAppMemoryDbContext> databaseProvider)
public CityRepository(IMemoryDatabaseProvider<TestAppMemoryDbContext> databaseProvider)
: base(databaseProvider)
{
}
public Task<City> FindByNameAsync(string name)
public async Task<City> FindByNameAsync(string name)
{
return Task.FromResult(Collection.FirstOrDefault(c => c.Name == name));
return (await GetCollectionAsync()).FirstOrDefault(c => c.Name == name);
}
public async Task<List<Person>> GetPeopleInTheCityAsync(string cityName)
{
var city = await FindByNameAsync(cityName);
return Database.Collection<Person>().Where(p => p.CityId == city.Id).ToList();
return (await GetDatabaseAsync()).Collection<Person>().Where(p => p.CityId == city.Id).ToList();
}
}
}

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.MultiTenancy;
@ -49,28 +50,28 @@ namespace Volo.Abp.Data.MultiTenancy
}
[Fact]
public void All_Tests()
public async Task All_Tests()
{
//No tenant in current context
_connectionResolver.Resolve().ShouldBe("default-value");
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
(await _connectionResolver.ResolveAsync()).ShouldBe("default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value");
//Overrided connection strings for tenant1
//Overriden connection strings for tenant1
using (_currentTenant.Change(_tenant1Id))
{
_connectionResolver.Resolve().ShouldBe("tenant1-default-value");
_connectionResolver.Resolve("db1").ShouldBe("tenant1-db1-value");
(await _connectionResolver.ResolveAsync()).ShouldBe("tenant1-default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("tenant1-db1-value");
}
//No tenant in current context
_connectionResolver.Resolve().ShouldBe("default-value");
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
(await _connectionResolver.ResolveAsync()).ShouldBe("default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value");
//Undefined connection strings for tenant2
using (_currentTenant.Change(_tenant2Id))
{
_connectionResolver.Resolve().ShouldBe("default-value");
_connectionResolver.Resolve("db1").ShouldBe("db1-default-value");
(await _connectionResolver.ResolveAsync()).ShouldBe("default-value");
(await _connectionResolver.ResolveAsync("db1")).ShouldBe("db1-default-value");
}
}
}

@ -9,30 +9,32 @@ namespace Volo.Abp.TenantManagement
public interface ITenantRepository : IBasicRepository<Tenant, Guid>
{
Task<Tenant> FindByNameAsync(
string name,
bool includeDetails = true,
string name,
bool includeDetails = true,
CancellationToken cancellationToken = default);
[Obsolete("Use FindByNameAsync method.")]
Tenant FindByName(
string name,
bool includeDetails = true
);
[Obsolete("Use FindAsync method.")]
Tenant FindById(
Guid id,
bool includeDetails = true
);
Task<List<Tenant>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default);
Task<long> GetCountAsync(
string filter = null,
string filter = null,
CancellationToken cancellationToken = default);
}
}
}

@ -15,7 +15,7 @@ namespace Volo.Abp.TenantManagement
protected ICurrentTenant CurrentTenant { get; }
public TenantStore(
ITenantRepository tenantRepository,
ITenantRepository tenantRepository,
IObjectMapper<AbpTenantManagementDomainModule> objectMapper,
ICurrentTenant currentTenant)
{
@ -52,6 +52,7 @@ namespace Volo.Abp.TenantManagement
}
}
[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(string name)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
@ -66,6 +67,7 @@ namespace Volo.Abp.TenantManagement
}
}
[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(Guid id)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!

@ -28,6 +28,7 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore
.FirstOrDefaultAsync(t => t.Name == name, GetCancellationToken(cancellationToken));
}
[Obsolete("Use FindByNameAsync method.")]
public virtual Tenant FindByName(string name, bool includeDetails = true)
{
return DbSet
@ -35,6 +36,7 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore
.FirstOrDefault(t => t.Name == name);
}
[Obsolete("Use FindAsync method.")]
public virtual Tenant FindById(Guid id, bool includeDetails = true)
{
return DbSet

@ -28,12 +28,14 @@ namespace Volo.Abp.TenantManagement.MongoDB
.FirstOrDefaultAsync(t => t.Name == name, GetCancellationToken(cancellationToken));
}
[Obsolete("Use FindByNameAsync method.")]
public virtual Tenant FindByName(string name, bool includeDetails = true)
{
return GetMongoQueryable()
.FirstOrDefault(t => t.Name == name);
}
[Obsolete("Use FindAsync method.")]
public virtual Tenant FindById(Guid id, bool includeDetails = true)
{
return GetMongoQueryable()

Loading…
Cancel
Save