From a2e246f5b9c5eb5d4a4655950156d9455b2df8b1 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 22:58:36 +0800 Subject: [PATCH 01/14] feat(UoW): CreateDbContext supports lazy loading --- .../DispatcherOptionsExtensions.cs | 2 +- src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs | 14 ++++++-------- .../Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs | 13 +++++++++++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index 5f38ec2f5..338a53833 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -40,7 +40,7 @@ private static IServiceCollection UseUoW( services.AddSingleton(); services.TryAddScoped(); - services.TryAddSingleton(); + services.TryAddSingleton>(); services.TryAddScoped(); services.TryAddSingleton(); diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs index ff9d5cdad..3a25e74c8 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs @@ -4,7 +4,9 @@ public class UnitOfWork : IUnitOfWork where TDbContext : MasaDbConte { public IServiceProvider ServiceProvider { get; } - protected DbContext Context; + private readonly DbContext? _context = null; + + protected DbContext Context => _context ?? ServiceProvider.GetRequiredService(); public DbTransaction Transaction { @@ -30,11 +32,7 @@ public DbTransaction Transaction public bool UseTransaction { get; set; } = true; - public UnitOfWork(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider; - Context = serviceProvider.GetRequiredService(); - } + public UnitOfWork(IServiceProvider serviceProvider) => ServiceProvider = serviceProvider; public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { @@ -59,7 +57,7 @@ public async Task RollbackAsync(CancellationToken cancellationToken = default) await Context.Database.RollbackTransactionAsync(cancellationToken); } - public async ValueTask DisposeAsync() => await Context.DisposeAsync(); + public async ValueTask DisposeAsync() => await (_context?.DisposeAsync() ?? ValueTask.CompletedTask); - public void Dispose() => Context.Dispose(); + public void Dispose() => _context?.Dispose(); } diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs index 8edadd18d..7601de4d7 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs @@ -1,14 +1,22 @@ namespace Masa.Contrib.Data.UoW.EF; -public class UnitOfWorkManager : IUnitOfWorkManager +public class UnitOfWorkManager : IUnitOfWorkManager where TDbContext : MasaDbContext { private readonly IServiceProvider _serviceProvider; public UnitOfWorkManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; - public IUnitOfWork CreateDbContext() + /// + /// + /// + /// Deferred creation of DbContext, easy to specify tenant or environment by yourself, which is very effective for physical isolation + /// + public IUnitOfWork CreateDbContext(bool lazyLoading = true) { var scope = _serviceProvider.CreateAsyncScope(); + if (!lazyLoading) + scope.ServiceProvider.GetRequiredService(); + return scope.ServiceProvider.GetRequiredService(); } @@ -21,6 +29,7 @@ public IUnitOfWork CreateDbContext(MasaDbContextConfigurationOptions options) var scope = _serviceProvider.CreateAsyncScope(); var unitOfWorkAccessor = scope.ServiceProvider.GetRequiredService(); unitOfWorkAccessor.CurrentDbContextOptions = options; + return scope.ServiceProvider.GetRequiredService(); } } From 21810d98e2ec5cdf155dcc92620d5a50a2a2ffb2 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 22:59:45 +0800 Subject: [PATCH 02/14] feat: support Isolation --- .../EnvironmentContext.cs | 8 ++ .../EnvironmentSaveChangesFilter.cs | 23 +++++ .../IsolationBuilderExtensions.cs | 13 +++ .../Masa.Contrib.Isolation.Environment.csproj | 17 ++++ .../README.md | 88 ++++++++++++++++++ .../README.zh-CN.md | 89 ++++++++++++++++++ .../_Imports.cs | 7 ++ .../ConvertProvider.cs | 18 ++++ .../IsolationBuilderExtensions.cs | 16 ++++ ...Masa.Contrib.Isolation.MultiTenancy.csproj | 17 ++++ .../README.md | 91 ++++++++++++++++++ .../README.zh-CN.md | 91 ++++++++++++++++++ .../TenancySaveChangesFilter.cs | 26 ++++++ .../TenantContext.cs | 8 ++ .../_Imports.cs | 9 ++ .../DefaultConnectionStringProvider.cs | 88 ++++++++++++++++++ .../DispatcherOptionsExtensions.cs | 93 +++++++++++++++++++ .../Internal/Const.cs | 6 ++ .../Internal/TypeCommon.cs | 10 ++ .../IsolationBuilder.cs | 62 +++++++++++++ .../IsolationBuilderExtensions.cs | 10 ++ .../IsolationDbContext.cs | 64 +++++++++++++ .../IsolationDbContextProvider.cs | 20 ++++ .../Masa.Contrib.Isolation.UoW.EF.csproj | 21 +++++ .../Middleware/EnvironmentMiddleware.cs | 44 +++++++++ .../Middleware/IIsolationMiddleware.cs | 6 ++ .../Middleware/IsolationMiddleware.cs | 41 ++++++++ .../Middleware/TenancyMiddleware.cs | 43 +++++++++ .../EnvironmentVariablesParserProvider.cs | 19 ++++ .../CookieTenantParserProvider.cs | 24 +++++ .../MultiTenancy/FormTenantParserProvider.cs | 26 ++++++ .../HeaderTenantParserProvider.cs | 24 +++++ .../HttpContextItemTenantParserProvider.cs | 24 +++++ .../QueryStringTenantParserProvider.cs | 23 +++++ .../MultiTenancy/RouteTenantParserProvider.cs | 22 +++++ .../Masa.Contrib.Isolation.UoW.EF/README.md | 69 ++++++++++++++ .../README.zh-CN.md | 71 ++++++++++++++ .../Masa.Contrib.Isolation.UoW.EF/_Imports.cs | 25 +++++ 38 files changed, 1356 insertions(+) create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/README.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md create mode 100644 src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs new file mode 100644 index 000000000..696d4491e --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentContext.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Isolation.Environment; + +public class EnvironmentContext : IEnvironmentContext, IEnvironmentSetter +{ + public string CurrentEnvironment { get; private set; } = string.Empty; + + public void SetEnvironment(string environment) => CurrentEnvironment = environment; +} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs new file mode 100644 index 000000000..548df8976 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Isolation.Environment; + +public class EnvironmentSaveChangesFilter: ISaveChangesFilter +{ + private readonly IEnvironmentContext _environmentContext; + + public EnvironmentSaveChangesFilter(IEnvironmentContext environmentContext) + { + _environmentContext = environmentContext; + } + + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) + { + if (entity.Entity is IEnvironment) + { + entity.CurrentValues[nameof(IEnvironment.Environment)] = _environmentContext.CurrentEnvironment; + } + } + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs new file mode 100644 index 000000000..fed35bef8 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/IsolationBuilderExtensions.cs @@ -0,0 +1,13 @@ +namespace Masa.Contrib.Isolation.Environment; + +public static class IsolationBuilderExtensions +{ + public static IIsolationBuilder UseEnvironment(this IIsolationBuilder isolationBuilder) + { + isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(EnvironmentSaveChangesFilter), ServiceLifetime.Scoped)); + isolationBuilder.Services.TryAddScoped(); + isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentContext), serviceProvider => serviceProvider.GetRequiredService()); + isolationBuilder.Services.TryAddScoped(typeof(IEnvironmentSetter), serviceProvider => serviceProvider.GetRequiredService()); + return isolationBuilder; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj b/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj new file mode 100644 index 000000000..b1f9910a2 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md new file mode 100644 index 000000000..d30933e75 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md @@ -0,0 +1,88 @@ +[中](README.zh-CN.md) | EN + +## Masa.Contrib.Isolation.Environment + +Example: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.Environment // Environmental isolation Quote on demand +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` +* 1.1 When the current environment is development: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.2 When the current environment is staging: database address: server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 When the current environment is another environment: database address: server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; + +2. Using Isolation.UoW.EF +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseEnvironment());// Use environment isolation +}); +```` + +3. DbContext needs to inherit IsolationDbContext + +```` C# +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} +```` + +4. The class corresponding to the isolated table needs to implement IEnvironment + +You can also choose not to implement IMultiTenant when using physical isolation + +##### Summarize + +* How is the environment resolved in the controller or MinimalAPI? + * The environment provides 1 parser by default, and the execution order is: EnvironmentVariablesParserProvider (obtained in the system environment variable, the default environment parameter: ASPNETCORE_ENVIRONMENT) +* If the parser fails to resolve the environment, what is the last database used? + * If the parsing environment fails, return DefaultConnection directly +* How to change the default environment parameter name + +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetEnvironmentKey("env").UseEnvironment());// Use environment isolation +}); +```` +* How to change the parser + +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() + { + new EnvironmentVariablesParserProvider() + }).UseEnvironment());// Use environment isolation +}); +```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md new file mode 100644 index 000000000..c1f8f45c1 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md @@ -0,0 +1,89 @@ +中 | [EN](README.md) + +## Masa.Contrib.Isolation.Environment + +用例: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.Environment // 环境隔离 按需引用 +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "Environment": "staging", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` + +* 1.1 当前环境是development时:数据库地址:server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.2 当前环境是staging时:数据库地址:server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 当前环境是其他环境时:数据库地址:server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; + +2. 使用Isolation.UoW.EF +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseEnvironment());// 使用环境隔离 +}); +``` + +3. DbContext需要继承IsolationDbContext + +``` C# +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} +``` + +4. 隔离的表对应的类需要实现IEnvironment + +采用物理隔离时也可以选择不实现IMultiTenant + +##### 总结 + +* 控制器或MinimalAPI中环境如何解析? + * 环境默认提供了1个解析器,执行顺序为:EnvironmentVariablesParserProvider (在系统环境变量中获取,环境参数默认:ASPNETCORE_ENVIRONMENT) +* 如果解析器解析环境失败,那最后使用的数据库是? + * 若解析环境失败,则直接返回DefaultConnection +* 如何更改默认环境参数名 + +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetEnvironmentKey("env").UseEnvironment());// 使用环境隔离 +}); +``` +* 如何更改解析器 + +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() + { + new EnvironmentVariablesParserProvider() + }).UseEnvironment());// 使用环境隔离 +}); +``` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs new file mode 100644 index 000000000..aa48cf9b7 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/_Imports.cs @@ -0,0 +1,7 @@ +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.Utils.Data.EntityFrameworkCore.Filters; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs new file mode 100644 index 000000000..004f8362d --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.Isolation.MultiTenancy; + +public class ConvertProvider : IConvertProvider +{ + public object ChangeType(string value, Type conversionType) + { + object result; + if (conversionType == typeof(Guid)) + { + result = Guid.Parse(value); + } + else + { + result= Convert.ChangeType(value, conversionType); + } + return result; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs new file mode 100644 index 000000000..50f08a6ea --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs @@ -0,0 +1,16 @@ +namespace Masa.Contrib.Isolation.MultiTenancy; + +public static class IsolationBuilderExtensions +{ + public static IIsolationBuilder UseMultiTenancy(this IIsolationBuilder isolationBuilder) => isolationBuilder.UseMultiTenancy(); + + public static IIsolationBuilder UseMultiTenancy(this IIsolationBuilder isolationBuilder) where TKey : IComparable + { + isolationBuilder.Services.TryAddSingleton(); + isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(TenancySaveChangesFilter), ServiceLifetime.Scoped)); + isolationBuilder.Services.TryAddScoped(); + isolationBuilder.Services.TryAddScoped(typeof(ITenantContext), serviceProvider => serviceProvider.GetRequiredService()); + isolationBuilder.Services.TryAddScoped(typeof(ITenantSetter), serviceProvider => serviceProvider.GetRequiredService()); + return isolationBuilder; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj new file mode 100644 index 000000000..228b479bf --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md new file mode 100644 index 000000000..c3d814a1c --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md @@ -0,0 +1,91 @@ +[中](README.zh-CN.md) | EN + +## Masa.Contrib.Isolation.MultiTenancy + +Example: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.MultiTenancy // Multi-tenant isolation On-demand reference +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` + +* 1.1 When the current tenant is 00000000-0000-0000-0000-000000000002: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.2 When the current tenant is 00000000-0000-0000-0000-000000000003: database address: server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; + +2. Using Isolation.UoW.EF +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseMultiTenancy());// Use tenant isolation +}); +```` + +3. DbContext needs to inherit IsolationDbContext + +```` C# +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} +```` + +4. The class corresponding to the isolated table needs to implement IMultiTenant + +You can also choose not to implement IMultiTenant when using physical isolation + +> The tenant id type can be specified by yourself, the default Guid type +> * For example: Implement IMultiTenant to implement IMultiTenant, UseMultiTenancy() to UseMultiTenancy() + +##### Summarize + +* How to resolve the tenant in the controller or MinimalAPI? + * The tenant provides 6 parsers by default, HttpContextItemTenantParserProvider、the execution order is: QueryStringTenantParserProvider, FormTenantParserProvider, RouteTenantParserProvider, HeaderTenantParserProvider, CookieTenantParserProvider (tenant parameter default: __tenant) +* If the resolver fails to resolve the tenant, what is the last database used? + * If the resolution of the tenant fails, return the DefaultConnection directly +* How to change the default tenant parameter name + +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenancy());// Use tenant isolation +}); +```` +* How to change the parser + +```` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetTenantParsers(new List() + { + new QueryStringTenantParserProvider() + }).UseMultiTenancy());// Use tenant isolation +}); +```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md new file mode 100644 index 000000000..e3f67ffac --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md @@ -0,0 +1,91 @@ +中 | [EN](README.md) + +## Masa.Contrib.Isolation.MultiTenancy + +用例: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.MultiTenancy // 多租户隔离 按需引用 +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + }, + { + "TenantId": "00000000-0000-0000-0000-000000000003", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` + +* 1.1 当前租户为00000000-0000-0000-0000-000000000002时:数据库地址:server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.2 当前租户为00000000-0000-0000-0000-000000000003时:数据库地址:server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; + +2. 使用Isolation.UoW.EF +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseMultiTenancy());// 使用租户隔离 +}); +``` + +3. DbContext需要继承IsolationDbContext + +``` C# +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} +``` + +4. 隔离的表对应的类需要实现IMultiTenant + +采用物理隔离时也可以选择不实现IMultiTenant + +> 租户id类型支持自行指定,默认Guid类型 +> * 如:实现IMultiTenant改为实现IMultiTenant,UseMultiTenancy()改为UseMultiTenancy() + +##### 总结 + +* 控制器或MinimalAPI中租户如何解析? + * 租户默认提供了6个解析器,执行顺序分别为:HttpContextItemTenantParserProvider、QueryStringTenantParserProvider、FormTenantParserProvider、RouteTenantParserProvider、HeaderTenantParserProvider、CookieTenantParserProvider (租户参数默认:__tenant) +* 如果解析器解析租户失败,那最后使用的数据库是? + * 若解析租户失败,则直接返回DefaultConnection +* 如何更改默认租户参数名 + +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenancy());// 使用租户隔离 +}); +``` +* 如何更改解析器 + +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.SetTenantParsers(new List() + { + new QueryStringTenantParserProvider() + }).UseMultiTenancy());// 使用租户隔离 +}); +``` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs new file mode 100644 index 000000000..c00eedbf5 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs @@ -0,0 +1,26 @@ +namespace Masa.Contrib.Isolation.MultiTenancy; + +public class TenancySaveChangesFilter : ISaveChangesFilter where TKey : IComparable +{ + private readonly ITenantContext _tenantContext; + private readonly IConvertProvider _convertProvider; + + public TenancySaveChangesFilter(ITenantContext tenantContext,IConvertProvider convertProvider) + { + _tenantContext = tenantContext; + _convertProvider = convertProvider; + } + + public void OnExecuting(ChangeTracker changeTracker) + { + changeTracker.DetectChanges(); + foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) + { + if (entity.Entity is IMultiTenant && _tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + { + object tenantId = _convertProvider.ChangeType(_tenantContext.CurrentTenant.Id, typeof(TKey)); + entity.CurrentValues[nameof(IMultiTenant.TenantId)] = tenantId; + } + } + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs new file mode 100644 index 000000000..972eec77a --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs @@ -0,0 +1,8 @@ +namespace Masa.Contrib.Isolation.MultiTenancy; + +public class TenantContext : ITenantContext, ITenantSetter +{ + public Tenant? CurrentTenant { get; private set; } + + public void SetTenant(Tenant tenant) => CurrentTenant = tenant; +} diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs new file mode 100644 index 000000000..10cf11801 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs @@ -0,0 +1,9 @@ +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.Utils.Data.EntityFrameworkCore.Filters; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.ChangeTracking; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using System.Linq; + diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs new file mode 100644 index 000000000..c3d4296ab --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs @@ -0,0 +1,88 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public class DefaultConnectionStringProvider : IConnectionStringProvider +{ + private readonly IUnitOfWorkAccessor _unitOfWorkAccessor; + private readonly IOptionsSnapshot _options; + private readonly IEnvironmentContext? _environmentContext; + private readonly ITenantContext? _tenantContext; + private readonly ILogger? _logger; + + public DefaultConnectionStringProvider( + IUnitOfWorkAccessor unitOfWorkAccessor, + IOptionsSnapshot options, + IEnvironmentContext? environmentContext = null, + ITenantContext? tenantContext = null, + ILogger? logger = null) + { + _unitOfWorkAccessor = unitOfWorkAccessor; + _options = options; + _environmentContext = environmentContext; + _tenantContext = tenantContext; + _logger = logger; + } + + public Task GetConnectionStringAsync() => Task.FromResult(GetConnectionString()); + + public string GetConnectionString() + { + if (_unitOfWorkAccessor.CurrentDbContextOptions != null) + return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; + + Expression> condition = option => true; + + if (_tenantContext != null) + { + if (_tenantContext.CurrentTenant == null) + { + _logger?.LogError($"Tenant resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); + return SetConnectionString(); + } + + condition = condition.And(option => option.TenantId == "*" || (_tenantContext.CurrentTenant!=null && _tenantContext.CurrentTenant.Id.Equals(option.TenantId, StringComparison.CurrentCultureIgnoreCase))); + } + + if (_environmentContext != null) + { + if (string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) + { + _logger?.LogError($"Environment resolution failed, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); + return SetConnectionString(); + } + + condition = condition.And(option => option.Environment == "*" || option.Environment.Equals(_environmentContext.CurrentEnvironment, StringComparison.CurrentCultureIgnoreCase)); + } + + string connectionString; + var list = _options.Value.Isolations.Where(condition.Compile()).ToList(); + if (list.Count >= 1) + { + connectionString = list.OrderByDescending(option=>option.Score).Select(option => option.ConnectionString).FirstOrDefault()!; + if (list.Count > 1) + _logger?.LogInformation($"{GetMessage()}, Matches multiple available database link strings, the currently used ConnectionString is [{connectionString}]"); + } + else + { + connectionString = _options.Value.DefaultConnection; + _logger?.LogDebug($"{GetMessage()}, the currently used ConnectionString is [{nameof(_options.Value.DefaultConnection)}]"); + } + return SetConnectionString(connectionString); + } + + private string SetConnectionString(string? connectionString = null) + { + _unitOfWorkAccessor.CurrentDbContextOptions = new MasaDbContextConfigurationOptions(connectionString ?? _options.Value.DefaultConnection); + return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; + } + + private string GetMessage() + { + StringBuilder stringBuilder = new StringBuilder(); + if (_environmentContext != null) + stringBuilder.Append($"Environment: [{_environmentContext.CurrentEnvironment}], "); + if (_tenantContext != null) + stringBuilder.Append($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}]"); + var message = stringBuilder.ToString(); + return message.Substring(0, message.Length - 1); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs new file mode 100644 index 000000000..cd9ff2f1f --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs @@ -0,0 +1,93 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public static class DispatcherOptionsExtensions +{ + public static IEventBusBuilder UseIsolationUoW( + this IEventBusBuilder eventBusBuilder, + Action? optionsBuilder, + Action isolationBuilder, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + { + eventBusBuilder.Services.UseIsolationUoW(nameof(eventBusBuilder.Services), isolationBuilder); + return eventBusBuilder.UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); + } + + public static IDispatcherOptions UseIsolationUoW( + this IDispatcherOptions options, + Action? optionsBuilder, + Action isolationBuilder, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + { + options.Services.UseIsolationUoW(nameof(options.Services), isolationBuilder); + return options.UseUoW(optionsBuilder, disableRollbackOnFailure, useTransaction); + } + + private static IServiceCollection UseIsolationUoW( + this IServiceCollection services, + string paramName, + Action isolationBuilder) + { + ArgumentNullException.ThrowIfNull(services, paramName); + ArgumentNullException.ThrowIfNull(isolationBuilder); + + if (services.Any(service => service.ImplementationType == typeof(IsolationUoWProvider))) + return services; + + services.AddSingleton(); + + IsolationBuilder builder = new IsolationBuilder(services); + isolationBuilder.Invoke(builder); + + if (services.Count(service => service.ServiceType == typeof(ITenantContext) || service.ServiceType == typeof(IEnvironmentContext)) < 1) + throw new NotSupportedException("Tenant isolation and environment isolation use at least one"); + + services.Configure(option => + { + option.TenantKey = builder.TenantKey; + option.EnvironmentKey = builder.EnvironmentKey; + }); + + services.AddHttpContextAccessor(); + + if (services.Any(service => service.ServiceType == typeof(ITenantContext))) + services.AddScoped(serviceProvider => new TenancyMiddleware(serviceProvider, builder.TenantParsers)); + + if (services.Any(service => service.ServiceType == typeof(IEnvironmentContext))) + services.AddScoped(serviceProvider => new EnvironmentMiddleware(serviceProvider, builder.EnvironmentParsers)); + + services.AddTransient(typeof(IMiddleware<>), typeof(IsolationMiddleware<>)); + services.TryAddSingleton(); + services.TryAddConfigure(Const.DEFAULT_SECTION); + services.TryAddScoped(typeof(IConnectionStringProvider), typeof(DefaultConnectionStringProvider)); + return services; + } + + private static IServiceCollection TryAddConfigure( + this IServiceCollection services, + string sectionName) + where TOptions : class + { + IConfiguration? configuration = services.BuildServiceProvider().GetService(); + if (configuration == null) + return services; + + string name = Options.DefaultName; + services.AddOptions(); + var configurationSection = configuration.GetSection(sectionName); + services.TryAddSingleton>( + new ConfigurationChangeTokenSource(name, configurationSection)); + services.TryAddSingleton>(new NamedConfigureFromConfigurationOptions(name, + configurationSection, _ => + { + })); + return services; + } + + private class IsolationUoWProvider + { + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs new file mode 100644 index 000000000..7e66d8486 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/Const.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Internal; + +internal class Const +{ + public const string DEFAULT_SECTION = "ConnectionStrings"; +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs new file mode 100644 index 000000000..7a583b561 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Internal; + +internal static class TypeCommon +{ + static bool IsConcrete(this Type type) => !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; + + public static bool IsGenericInterfaceAssignableFrom(this Type genericType, Type type) => + type.IsConcrete() && + type.GetInterfaces().Any(t => t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == genericType); +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs new file mode 100644 index 000000000..26eac15d7 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilder.cs @@ -0,0 +1,62 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public class IsolationBuilder : IIsolationBuilder +{ + public IServiceCollection Services { get; } + + public string EnvironmentKey { get; private set; } + + public string TenantKey { get; private set;} + + private List _tenantParsers; + + public IReadOnlyCollection TenantParsers => _tenantParsers; + + private List _environmentParsers; + + public IReadOnlyCollection EnvironmentParsers => _environmentParsers; + + public IsolationBuilder(IServiceCollection services) + { + Services = services; + EnvironmentKey = "ASPNETCORE_ENVIRONMENT"; + TenantKey = "__tenant"; + _tenantParsers = new List() + { + new HttpContextItemTenantParserProvider(), + new QueryStringTenantParserProvider(), + new FormTenantParserProvider(), + new RouteTenantParserProvider(), + new HeaderTenantParserProvider(), + new CookieTenantParserProvider() + }; + _environmentParsers = new List() + { + new EnvironmentVariablesParserProvider() + }; + } + + public IsolationBuilder SetEnvironmentKey(string environmentKey) + { + EnvironmentKey = environmentKey; + return this; + } + + public IsolationBuilder SetTenantKey(string tenantKey) + { + TenantKey = tenantKey; + return this; + } + + public IsolationBuilder SetTenantParsers(List tenantParserProviders) + { + _tenantParsers = tenantParserProviders; + return this; + } + + public IsolationBuilder SetEnvironmentParsers(List environmentParserProviders) + { + _environmentParsers = environmentParserProviders; + return this; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs new file mode 100644 index 000000000..5d48710d4 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationBuilderExtensions.cs @@ -0,0 +1,10 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public static class IsolationBuilderExtensions +{ + public static TApplicationBuilder UseIsolation(this TApplicationBuilder app) where TApplicationBuilder : IApplicationBuilder + { + app.UseMiddleware(); + return app; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs new file mode 100644 index 000000000..4ba5f92ee --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs @@ -0,0 +1,64 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public abstract class IsolationDbContext : IsolationDbContext +{ + protected IsolationDbContext(MasaDbContextOptions options) : base(options) + { + } +} + +/// +/// DbContext providing isolation +/// +/// tenant id type +public abstract class IsolationDbContext : MasaDbContext + where TKey : IComparable +{ + private readonly IEnvironmentContext? _environmentContext; + private readonly ITenantContext? _tenantContext; + private readonly ILogger>? _logger; + + public IsolationDbContext(MasaDbContextOptions options) : base(options) + { + _environmentContext = options.ServiceProvider.GetService(); + _tenantContext = options.ServiceProvider.GetService(); + _logger = options.ServiceProvider.GetService>>(); + } + + protected override Expression>? CreateFilterExpression() + where TEntity : class + { + Expression>? expression = null; + + if (typeof(IMultiTenant<>).IsGenericInterfaceAssignableFrom(typeof(TEntity)) && _tenantContext != null) + { + Expression> tenantFilter = entity => !IsTenantFilterEnabled || + _tenantContext.CurrentTenant == null || + _tenantContext.CurrentTenant.Id == string.Empty || + _tenantContext.CurrentTenant.Id == null! || + Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() == + (_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : default(TKey)!.ToString()); + expression = tenantFilter.And(expression != null, expression); + } + + if (typeof(IEnvironment).IsAssignableFrom(typeof(TEntity)) && _environmentContext != null) + { + Expression> envFilter = entity => !IsEnvironmentFilterEnabled || + _environmentContext.CurrentEnvironment == string.Empty || + _environmentContext.CurrentEnvironment == null! || + Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IEnvironment.Environment)) + .Equals(_environmentContext == null ? default : _environmentContext.CurrentEnvironment); + expression = envFilter.And(expression != null, expression); + } + + var secondExpression = base.CreateFilterExpression(); + if (secondExpression != null) + expression = secondExpression.And(expression != null, expression); + + return expression; + } + + protected virtual bool IsEnvironmentFilterEnabled => DataFilter?.IsEnabled() ?? false; + + protected virtual bool IsTenantFilterEnabled => DataFilter?.IsEnabled>() ?? false; +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs new file mode 100644 index 000000000..0e44917ca --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContextProvider.cs @@ -0,0 +1,20 @@ +namespace Masa.Contrib.Isolation.UoW.EF; + +public class IsolationDbContextProvider : BaseDbConnectionStringProvider +{ + private readonly IOptionsMonitor _options; + + public IsolationDbContextProvider(IOptionsMonitor options) => _options = options; + + protected override List GetDbContextOptionsList() + { + var connectionStrings = _options.CurrentValue.Isolations + .Select(connectionString => connectionString.ConnectionString) + .Distinct() + .ToList(); + if (!connectionStrings.Contains(_options.CurrentValue.DefaultConnection)) + connectionStrings.Add(_options.CurrentValue.DefaultConnection); + + return connectionStrings.Select(connectionString => new MasaDbContextConfigurationOptions(connectionString)).ToList(); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj new file mode 100644 index 000000000..59e598ccf --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Masa.Contrib.Isolation.UoW.EF.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs new file mode 100644 index 000000000..2143a7e67 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs @@ -0,0 +1,44 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Middleware; + +public class EnvironmentMiddleware : IIsolationMiddleware +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger? _logger; + private readonly IEnvironmentContext _environmentContext; + private readonly IEnumerable _environmentParserProviders; + private bool _handled; + + public EnvironmentMiddleware(IServiceProvider serviceProvider, IEnumerable environmentParserProviders) + { + _serviceProvider = serviceProvider; + _logger = _serviceProvider.GetService>(); + _environmentContext = _serviceProvider.GetRequiredService(); + _environmentParserProviders = environmentParserProviders; + } + + public async Task HandleAsync() + { + if(_handled) + return; + + if (!string.IsNullOrEmpty(_environmentContext.CurrentEnvironment)) + { + _logger?.LogDebug($"The environment is successfully resolved, and the resolver is: empty"); + return; + } + + List parsers = new(); + foreach (var environmentParserProvider in _environmentParserProviders) + { + parsers.Add(environmentParserProvider.Name); + if (await environmentParserProvider.ResolveAsync(_serviceProvider)) + { + _logger?.LogDebug($"The environment is successfully resolved, and the resolver is: {string.Join("、 ",parsers)}"); + _handled = true; + return; + } + } + _logger?.LogDebug($"Failed to resolve environment, and the resolver is: {string.Join("、 ",parsers)}"); + _handled = true; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs new file mode 100644 index 000000000..fc2398d39 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IIsolationMiddleware.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Middleware; + +public interface IIsolationMiddleware +{ + Task HandleAsync(); +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs new file mode 100644 index 000000000..8e367c50e --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/IsolationMiddleware.cs @@ -0,0 +1,41 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Middleware; + +public class IsolationMiddleware : IMiddleware where TEvent : IEvent +{ + private readonly IEnumerable _middlewares; + + public IsolationMiddleware(IEnumerable middlewares) + { + _middlewares = middlewares; + } + + public async Task HandleAsync(TEvent @event, EventHandlerDelegate next) + { + foreach (var middleware in _middlewares) + { + await middleware.HandleAsync(); + } + + await next(); + } +} + +public class IsolationMiddleware +{ + private readonly RequestDelegate _next; + + public IsolationMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext httpContext, IEnumerable middlewares) + { + foreach (var middleware in middlewares) + { + await middleware.HandleAsync(); + } + + await _next(httpContext); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs new file mode 100644 index 000000000..588033ce9 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs @@ -0,0 +1,43 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Middleware; + +public class TenancyMiddleware : IIsolationMiddleware +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger? _logger; + private readonly ITenantContext _tenantContext; + private readonly IEnumerable _tenantParserProviders; + private bool _handled; + public TenancyMiddleware(IServiceProvider serviceProvider, IEnumerable tenantParserProviders) + { + _serviceProvider = serviceProvider; + _logger = _serviceProvider.GetService>(); + _tenantContext = _serviceProvider.GetRequiredService(); + _tenantParserProviders = tenantParserProviders; + } + + public async Task HandleAsync() + { + if(_handled) + return; + + if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + { + _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: empty"); + return; + } + + List parsers = new(); + foreach (var tenantParserProvider in _tenantParserProviders) + { + parsers.Add(tenantParserProvider.Name); + if (await tenantParserProvider.ExecuteAsync(_serviceProvider)) + { + _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: {string.Join("、 ", parsers)}"); + _handled = true; + return; + } + } + _logger?.LogDebug($"Failed to resolve tenant, and the resolver is: {string.Join("、 ", parsers)}"); + _handled = true; + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs new file mode 100644 index 000000000..066c7ab78 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/Environment/EnvironmentVariablesParserProvider.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.Environment; + +public class EnvironmentVariablesParserProvider : IEnvironmentParserProvider +{ + public string Name { get; } = "EnvironmentVariables"; + + public Task ResolveAsync(IServiceProvider serviceProvider) + { + var environmentSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + string? environment = System.Environment.GetEnvironmentVariable(options.Value.EnvironmentKey); + if (environment != null && !string.IsNullOrEmpty(environment)) + { + environmentSetter.SetEnvironment(environment); + return Task.FromResult(true); + } + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs new file mode 100644 index 000000000..e3f15a8d3 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class CookieTenantParserProvider : ITenantParserProvider +{ + public string Name => "Route"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + if (httpContext?.Request.Cookies.ContainsKey(options.Value.TenantKey) ?? false) + { + var tenantId = httpContext.Request.Cookies[options.Value.TenantKey]!; + if (!string.IsNullOrEmpty(tenantId)) + { + tenantSetter.SetTenant(new Tenant(tenantId)); + return Task.FromResult(true); + } + } + + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs new file mode 100644 index 000000000..61013d850 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs @@ -0,0 +1,26 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class FormTenantParserProvider : ITenantParserProvider +{ + public string Name => "Form"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + if (!(httpContext?.Request.HasFormContentType ?? false)) + return Task.FromResult(false); + + if (httpContext?.Request.Form.ContainsKey(options.Value.TenantKey) ?? false) + { + var tenantId = httpContext.Request.Form[options.Value.TenantKey].ToString(); + if (!string.IsNullOrEmpty(tenantId)) + { + tenantSetter.SetTenant(new Tenant(tenantId)); + return Task.FromResult(true); + } + } + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs new file mode 100644 index 000000000..950e8c22b --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class HeaderTenantParserProvider : ITenantParserProvider +{ + public string Name => "Header"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + if (httpContext?.Request.Headers.ContainsKey(options.Value.TenantKey) ?? false) + { + var tenantId = httpContext.Request.Headers[options.Value.TenantKey].ToString(); + if (!string.IsNullOrEmpty(tenantId)) + { + tenantSetter.SetTenant(new Tenant(tenantId)); + return Task.FromResult(true); + } + } + + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs new file mode 100644 index 000000000..b624a8f8f --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs @@ -0,0 +1,24 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class HttpContextItemTenantParserProvider : ITenantParserProvider +{ + public string Name => "Items"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + if (httpContext?.Items.ContainsKey(options.Value.TenantKey) ?? false) + { + var tenantId = httpContext.Items[options.Value.TenantKey]?.ToString() ?? string.Empty; + if (!string.IsNullOrEmpty(tenantId)) + { + tenantSetter.SetTenant(new Tenant(tenantId)); + return Task.FromResult(true); + } + } + + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs new file mode 100644 index 000000000..27c5d6f51 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs @@ -0,0 +1,23 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class QueryStringTenantParserProvider : ITenantParserProvider +{ + public string Name => "QueryString"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + if (httpContext?.Request.Query.ContainsKey(options.Value.TenantKey) ?? false) + { + var tenantId = httpContext.Request.Query[options.Value.TenantKey].ToString(); + if (!string.IsNullOrEmpty(tenantId)) + { + tenantSetter.SetTenant(new Tenant(tenantId)); + return Task.FromResult(true); + } + } + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs new file mode 100644 index 000000000..2024b8007 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs @@ -0,0 +1,22 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + +public class RouteTenantParserProvider : ITenantParserProvider +{ + public string Name => "Route"; + + public Task ExecuteAsync(IServiceProvider serviceProvider) + { + var httpContext = serviceProvider.GetService()?.HttpContext; + var tenantSetter = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetRequiredService>(); + var tenantId = httpContext?.GetRouteValue(options.Value.TenantKey); + if (tenantId != null) + { + var tenantIdStr = tenantId.ToString(); + tenantSetter.SetTenant(new Tenant(tenantIdStr!)); + return Task.FromResult(true); + } + + return Task.FromResult(false); + } +} diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md new file mode 100644 index 000000000..693a90db9 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -0,0 +1,69 @@ +[中](README.zh-CN.md) | EN + +## Masa.Contrib.Isolation.UoW.EF + +Example: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.Environment // Environmental isolation Quote on demand +Install-Package Masa.Contrib.Isolation.MultiTenancy // Multi-tenant isolation On-demand reference +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "TenantId": "*",// match all tenants + "Environment": "development", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;", + "Score": 99 // When multiple environments are matched according to the conditions, the highest one is selected as the link address of the current DbContext according to the descending order of scores. The default Score is 100. + }, + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` + +* 1.1 When the current environment is equal to development: + * When the tenant is equal to 00000000-0000-0000-0000-000000000002, the database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; + * When the tenant is not equal to 00000000-0000-0000-0000-000000000002, the database address: server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; + +* 1.2 When the environment is not equal to development: + * No tenant distinction, database address: server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; + +2. 使用Isolation.UoW.EF +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenancy());// Select usage environment or tenant isolation as needed +}); +``` + +3. optional use: + +``` C# +var app = builder.Build(); +app.UseIsolation(); +``` + +> When the DbContext is not operated in the EventHandler, it needs to be used when the DbContext is directly operated in the Controller or Minimal, otherwise the environment and tenant acquisition will fail, resulting in the database address being selected as the default DefaultConnection address + +4. The class corresponding to the isolated table needs to implement IMultiTenant or IEnvironment + +Tenant isolation implements IMultiTenant, and environment isolation implements IEnvironment + +##### Summarize +* When are tenants and environments resolved? + * Manipulating DbContext, Repository, etc. in EventHandler can be used directly without adding. The environment and tenant information will be parsed when the Event is published. If the environment or tenant has been assigned, the parsing will be skipped + * To operate DbContext, Repository, etc. directly in the controller or MinimalAPI, you need to add `app.UseIsolation();` in Program.cs, the environment and tenant information will be parsed in the middleware of AspNetCore, if the environment or tenant has been assigned, will skip parsing \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md new file mode 100644 index 000000000..1b5f54e48 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -0,0 +1,71 @@ +中 | [EN](README.md) + +## Masa.Contrib.Isolation.UoW.EF + +用例: + +```C# +Install-Package Masa.Contrib.Isolation.UoW.EF +Install-Package Masa.Contrib.Isolation.Environment // 环境隔离 按需引用 +Install-Package Masa.Contrib.Isolation.MultiTenancy // 多租户隔离 按需引用 +Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer +``` + +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity;", + "Isolations": [ + { + "TenantId": "*",//匹配所有租户 + "Environment": "development", + "ConnectionString": "server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity;", + "Score": 99 //当根据条件匹配到多个环境时,根据分值降序选择其中最高的作为当前DbContext的链接地址,Score默认为100 + }, + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "Environment": "development", + "ConnectionString": "server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity;" + } + ] + } +} +``` + +* 1.1 当前环境等于development时: + * 当租户等于00000000-0000-0000-0000-000000000002时,数据库地址:server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; + * 当租户不等于00000000-0000-0000-0000-000000000002时,数据库地址:server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; + +* 1.2 当环境不等于development时: + * 不区分租户,数据库地址:server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; + +2. 使用Isolation.UoW.EF +``` C# +builder.Services.AddEventBus(eventBusBuilder => +{ + eventBusBuilder.UseIsolationUoW( + dbOptions => dbOptions.UseSqlServer(), + isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenancy());// 按需选择使用环境或者租户隔离 +}); +``` + +3. DbContext需要继承IsolationDbContext + +``` C# +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} +``` + +4. 隔离的表对应的类需要实现IMultiTenant或IEnvironment + +租户隔离实现IMultiTenant、环境隔离实现IEnvironment + +##### 总结 +* 租户与环境什么时候被解析? + * 在EventHandler中操作DbContext、Repository等可直接使用,无需添加,在Event被Publish会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 + * 直接在控制器或MinimalAPI中操作DbContext、Repository等需要在Program.cs中添加`app.UseIsolation();`,在AspNetCore的中间件中会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs new file mode 100644 index 000000000..6ab70aec1 --- /dev/null +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs @@ -0,0 +1,25 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Data.UoW.Options; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.BuildingBlocks.Isolation.Options; +global using Masa.Contrib.Data.UoW.EF; +global using Masa.Contrib.Isolation.UoW.EF.Internal; +global using Masa.Contrib.Isolation.UoW.EF.Middleware; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Routing; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using System.Linq.Expressions; +global using System.Reflection; +global using System.Text; +global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; +global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; + From c2e2365055f65ac48b2244d0060400b217dadb47 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 23:01:59 +0800 Subject: [PATCH 03/14] test: add Isolation UnitTest --- Masa.Contrib.sln | 79 +++++ .../CustomDbContext.cs | 39 +++ ...Masa.Contrib.Isolation.UoW.EF.Tests.csproj | 35 ++ .../TestBase.cs | 18 ++ .../TestIsolation.cs | 306 ++++++++++++++++++ .../_Imports.cs | 20 ++ .../appsettings.json | 18 ++ .../CustomDbContext.cs | 49 +++ .../EdgeDriverTest.cs | 43 +++ .../EventHandlers/RegisterUserEventHandler.cs | 51 +++ .../Events/RegisterUserEvent.cs | 5 + ....Contrib.Isolation.UoW.EF.Web.Tests.csproj | 32 ++ .../TestBase.cs | 18 ++ .../_Imports.cs | 21 ++ .../appsettings.json | 18 ++ 15 files changed, 752 insertions(+) create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/appsettings.json create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/RegisterUserEvent.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/TestBase.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/appsettings.json diff --git a/Masa.Contrib.sln b/Masa.Contrib.sln index 90cd0517a..f282ed5b7 100644 --- a/Masa.Contrib.sln +++ b/Masa.Contrib.sln @@ -136,6 +136,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.SearchEngine.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.SearchEngine.AutoComplete.Tests", "test\Masa.Contrib.SearchEngine.AutoComplete.Tests\Masa.Contrib.SearchEngine.AutoComplete.Tests.csproj", "{31262D61-26A4-4302-968D-52B8DA4558CD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.Isolation", "src\BuildingBlocks\MASA.BuildingBlocks\src\Isolation\Masa.BuildingBlocks.Isolation\Masa.BuildingBlocks.Isolation.csproj", "{B689E82B-B3E8-4C83-B56C-D4C27206AAC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{0C25062D-60A5-4690-974A-CDA9619866B4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiTenancy", "src\Isolation\Masa.Contrib.Isolation.MultiTenancy\Masa.Contrib.Isolation.MultiTenancy.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.Environment", "src\Isolation\Masa.Contrib.Isolation.Environment\Masa.Contrib.Isolation.Environment.csproj", "{E9A3C62C-EA85-40C3-BB3B-7836D0337F63}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "temp", "temp", "{03E3863B-452A-4023-BE34-FC84BD5AB04D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication1", "temp\WebApplication1\WebApplication1.csproj", "{B0CF95DC-C094-47A3-B904-475857DA4A4B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Tests\Masa.Contrib.Isolation.UoW.EF.Tests.csproj", "{50551732-AAE0-4F77-B401-9DD02E226CDB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Web.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Web.Tests\Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj", "{7CF33D08-9468-464D-B52E-955A5667EE42}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -504,6 +520,62 @@ Global {31262D61-26A4-4302-968D-52B8DA4558CD}.Release|Any CPU.Build.0 = Release|Any CPU {31262D61-26A4-4302-968D-52B8DA4558CD}.Release|x64.ActiveCfg = Release|Any CPU {31262D61-26A4-4302-968D-52B8DA4558CD}.Release|x64.Build.0 = Release|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Debug|x64.ActiveCfg = Debug|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Debug|x64.Build.0 = Debug|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|Any CPU.Build.0 = Release|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|x64.ActiveCfg = Release|Any CPU + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6}.Release|x64.Build.0 = Release|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Debug|x64.Build.0 = Debug|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|Any CPU.Build.0 = Release|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|x64.ActiveCfg = Release|Any CPU + {0C25062D-60A5-4690-974A-CDA9619866B4}.Release|x64.Build.0 = Release|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Debug|x64.Build.0 = Debug|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|Any CPU.Build.0 = Release|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|x64.ActiveCfg = Release|Any CPU + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}.Release|x64.Build.0 = Release|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Debug|x64.Build.0 = Debug|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|Any CPU.Build.0 = Release|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.ActiveCfg = Release|Any CPU + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.Build.0 = Release|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|x64.Build.0 = Debug|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|Any CPU.Build.0 = Release|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|x64.ActiveCfg = Release|Any CPU + {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|x64.Build.0 = Release|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|x64.ActiveCfg = Debug|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|x64.Build.0 = Debug|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|Any CPU.Build.0 = Release|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|x64.ActiveCfg = Release|Any CPU + {50551732-AAE0-4F77-B401-9DD02E226CDB}.Release|x64.Build.0 = Release|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Debug|x64.Build.0 = Debug|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|Any CPU.Build.0 = Release|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|x64.ActiveCfg = Release|Any CPU + {7CF33D08-9468-464D-B52E-955A5667EE42}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -571,6 +643,13 @@ Global {4A052E17-4D9E-41EF-89A5-73B917053F8E} = {2BE750A5-8AC7-457C-9BB2-6E3D5E2D23B8} {3F8532EF-3DC9-45F8-9562-994ABE066585} = {8C39C640-0E8A-43A7-890C-9742B6B70AA4} {31262D61-26A4-4302-968D-52B8DA4558CD} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {B689E82B-B3E8-4C83-B56C-D4C27206AAC6} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1} + {0C25062D-60A5-4690-974A-CDA9619866B4} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {E9A3C62C-EA85-40C3-BB3B-7836D0337F63} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} + {B0CF95DC-C094-47A3-B904-475857DA4A4B} = {03E3863B-452A-4023-BE34-FC84BD5AB04D} + {50551732-AAE0-4F77-B401-9DD02E226CDB} = {38E6C400-90C0-493E-9266-C1602E229F1B} + {7CF33D08-9468-464D-B52E-955A5667EE42} = {38E6C400-90C0-493E-9266-C1602E229F1B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs new file mode 100644 index 000000000..7d7594738 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/CustomDbContext.cs @@ -0,0 +1,39 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Tests; + +public class CustomDbContext : MasaDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) { } + + public DbSet User { get; set; } + + protected override void OnModelCreatingExecuting(ModelBuilder builder) + { + builder.Entity(ConfigureUserEntry); + } + + void ConfigureUserEntry(EntityTypeBuilder builder) + { + builder.ToTable("Users"); + + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id) + .IsRequired(); + + builder.Property(e => e.Name) + .HasMaxLength(6) + .IsRequired(); + } +} + +public class Users +{ + public Guid Id { get; private set; } + + public string Name { get; set; } = default!; + + public Users() + { + this.Id = Guid.NewGuid(); + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj new file mode 100644 index 000000000..316d914ea --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj @@ -0,0 +1,35 @@ + + + + net6.0 + enable + false + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + Always + + + + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestBase.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestBase.cs new file mode 100644 index 000000000..02cc6e61d --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestBase.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Tests; + +public class TestBase : IDisposable +{ + protected readonly string _connectionString = "DataSource=:memory:"; + protected readonly SqliteConnection Connection; + + protected TestBase() + { + Connection = new SqliteConnection(_connectionString); + Connection.Open(); + } + + public void Dispose() + { + Connection.Close(); + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs new file mode 100644 index 000000000..1fd9b4f0e --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -0,0 +1,306 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Tests; + +[TestClass] +public class TestIsolation : TestBase +{ + private IServiceCollection _services; + + [TestInitialize] + public void Initialize() + { + _services = new ServiceCollection(); + } + + [TestMethod] + public void TestUseIsolationUoW() + { + Mock eventBuilder = new(); + eventBuilder.Setup(builder => builder.Services).Returns(_services).Verifiable(); + Assert.ThrowsException(() => + { + eventBuilder.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), _ => + { + }); + }, "Tenant isolation and environment isolation use at least one"); + } + + [TestMethod] + public void TestUseIsolationUoW2() + { + Mock eventBuilder = new(); + eventBuilder.Setup(builder => builder.Services).Returns(_services).Verifiable(); + Assert.ThrowsException(() => + { + eventBuilder.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), null!); + }); + } + + [TestMethod] + public void TestUseIsolationUoW3() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + Assert.ThrowsException(() => + { + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), _ => + { + }); + }, "Tenant isolation and environment isolation use at least one"); + } + + [TestMethod] + public void TestUseIsolationUoW4() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + Assert.ThrowsException(() => + { + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), null!); + }); + } + + [TestMethod] + public void TestUseIsolationUoWByUseEnvironment() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), + isolationBuilder => isolationBuilder.UseEnvironment()); + + var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); + Assert.IsNotNull(serviceProvider.GetService()); + Assert.IsNotNull(serviceProvider.GetService()); + } + + [TestMethod] + public void TestUseIsolationUoWByUseMultiEnvironment() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), + isolationBuilder => isolationBuilder.UseEnvironment().UseEnvironment()); + + var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseIsolationUoWByUseTenant() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), + isolationBuilder => isolationBuilder.UseMultiTenancy()); + + var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); + Assert.IsNotNull(serviceProvider.GetService()); + Assert.IsNotNull(serviceProvider.GetService()); + } + + [TestMethod] + public void TestUseIsolationUoWByUseMultiTenant() + { + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), + isolationBuilder => isolationBuilder.UseMultiTenancy().UseMultiTenancy()); + + var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + Assert.IsTrue(serviceProvider.GetServices().Count() == 1); + } + + [TestMethod] + public void TestUseIsolation() + { + var configurationRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .Build(); + _services.AddSingleton(configurationRoot); + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), + isolationBuilder => isolationBuilder.UseMultiTenancy().UseEnvironment()); + var serviceProvider = _services.BuildServiceProvider(); + var customDbContext = serviceProvider.GetRequiredService(); + var unitOfWorkAccessor = serviceProvider.GetRequiredService(); + var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; + Assert.IsNotNull(currentDbContextOptions); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "test1"); + + var unitOfWorkManager = serviceProvider.GetRequiredService(); + var unifOfWorkNew = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew = unifOfWorkNew.ServiceProvider.GetRequiredService(); + + Assert.IsNull(unitOfWorkAccessorNew.CurrentDbContextOptions); + + Assert.IsTrue(unifOfWorkNew.ServiceProvider.GetRequiredService().CurrentTenant == null); + + Assert.IsTrue(string.IsNullOrEmpty(unifOfWorkNew.ServiceProvider.GetRequiredService().CurrentEnvironment)); + + unifOfWorkNew.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); + Assert.IsTrue(unifOfWorkNew.ServiceProvider.GetRequiredService().CurrentTenant!.Id == + "00000000-0000-0000-0000-000000000002"); + unifOfWorkNew.ServiceProvider.GetRequiredService().SetEnvironment("dev"); + + Assert.IsTrue(unifOfWorkNew.ServiceProvider.GetRequiredService().CurrentEnvironment == "dev"); + + var dbContext = unifOfWorkNew.ServiceProvider.GetRequiredService(); + + Assert.IsTrue(GetDataBaseConnectionString(dbContext) == "test1" && + unitOfWorkAccessorNew.CurrentDbContextOptions!.ConnectionString == "test1"); + + var unifOfWorkNew2 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("development"); + var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test1" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test1"); + + var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); + unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("development"); + var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test2" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test2"); + + var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + unifOfWorkNew4.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); + unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("production"); + var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test3" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test3"); + } + + [TestMethod] + public void TestUseEnvironment() + { + _services.Configure(option => + { + option.DefaultConnection = "test4"; + option.Isolations = new List() + { + new() + { + ConnectionString = "test5", + Environment = "dev" + }, + new() + { + ConnectionString = "test6", + Environment = "pro" + } + }; + }); + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), + isolationBuilder => isolationBuilder.UseEnvironment()); + var serviceProvider = _services.BuildServiceProvider(); + var customDbContext = serviceProvider.GetRequiredService(); + var unitOfWorkAccessor = serviceProvider.GetRequiredService(); + var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; + Assert.IsNotNull(currentDbContextOptions); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "test4"); + + var unitOfWorkManager = serviceProvider.GetRequiredService(); + + var unifOfWorkNew2 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("dev"); + var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test5" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test5"); + + var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("pro"); + var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test6" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test6"); + + var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("staging"); + var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test4" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test4"); + } + + [TestMethod] + public void TestUseMultiTenancy() + { + _services.Configure(option => + { + option.DefaultConnection = "test7"; + option.Isolations = new List() + { + new() + { + ConnectionString = "test8", + TenantId = "1" + }, + new() + { + ConnectionString = "test9", + TenantId = "2" + } + }; + }); + Mock dispatcherOption = new(); + dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); + dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), + isolationBuilder => isolationBuilder.UseMultiTenancy()); + var serviceProvider = _services.BuildServiceProvider(); + var customDbContext = serviceProvider.GetRequiredService(); + var unitOfWorkAccessor = serviceProvider.GetRequiredService(); + var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; + Assert.IsNotNull(currentDbContextOptions); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "test7"); + + var unitOfWorkManager = serviceProvider.GetRequiredService(); + + var unifOfWorkNew2 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + unifOfWorkNew2.ServiceProvider.GetRequiredService().SetTenant(new Tenant("1")); + var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test8" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test8"); + + var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("2")); + var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test9" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test9"); + + var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); + var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + unifOfWorkNew4.ServiceProvider.GetRequiredService().SetTenant(null!); + var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test7" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test7"); + } + + [TestMethod] + public void TestIsolationBuilder() + { + var services = new ServiceCollection(); + var isolationBuilder = new IsolationBuilder(services); + Assert.IsTrue(isolationBuilder.EnvironmentKey == "ASPNETCORE_ENVIRONMENT"); + Assert.IsTrue(isolationBuilder.TenantKey == "__tenant"); + Assert.IsTrue(isolationBuilder.TenantParsers.Count == 6); + Assert.IsTrue(isolationBuilder.EnvironmentParsers.Count == 1); + + Assert.IsTrue(isolationBuilder.SetTenantKey("tenantId").TenantKey == "tenantId"); + Assert.IsTrue(isolationBuilder.SetEnvironmentKey("dev").EnvironmentKey == "dev"); + Assert.IsTrue(isolationBuilder.SetEnvironmentParsers(new List()).EnvironmentParsers.Count == 0); + Assert.IsTrue(isolationBuilder.SetTenantParsers(new List()).EnvironmentParsers.Count == 0); + } + + private string GetDataBaseConnectionString(CustomDbContext dbContext) => dbContext.Database.GetConnectionString()!; +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs new file mode 100644 index 000000000..aea137644 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -0,0 +1,20 @@ +global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Isolation; +global using Masa.BuildingBlocks.Isolation.Environment; +global using Masa.BuildingBlocks.Isolation.MultiTenant; +global using Masa.BuildingBlocks.Isolation.Options; +global using Masa.Contrib.Isolation.Environment; +global using Masa.Contrib.Isolation.MultiTenancy; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Masa.Utils.Data.EntityFrameworkCore.Sqlite; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Metadata.Builders; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Moq; +global using System; +global using System.IO; +global using System.Linq; diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/appsettings.json b/test/Masa.Contrib.Isolation.UoW.EF.Tests/appsettings.json new file mode 100644 index 000000000..d60224647 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/appsettings.json @@ -0,0 +1,18 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "data source=test1", + "Isolations": [ + { + "TenantId": "*", + "Environment": "development", + "ConnectionString": "data source=test2", + "Score": 99 + }, + { + "TenantId": "00000000-0000-0000-0000-000000000002", + "Environment": "production", + "ConnectionString": "data source=test3" + } + ] + } +} \ No newline at end of file diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs new file mode 100644 index 000000000..75150cd4c --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/CustomDbContext.cs @@ -0,0 +1,49 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests; + +public class CustomDbContext : IsolationDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) { } + + public DbSet User { get; set; } + + protected override void OnModelCreatingExecuting(ModelBuilder builder) + { + builder.Entity(ConfigureUserEntry); + } + + void ConfigureUserEntry(EntityTypeBuilder builder) + { + builder.ToTable("Users"); + + builder.HasKey(e => e.Id); + + builder.Property(e => e.Id) + .IsRequired(); + + builder.Property(e => e.Account) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(e => e.Account) + .HasMaxLength(50) + .IsRequired(); + } +} + +public class Users : IIsolation +{ + public Guid Id { get; private set; } + + public string Account { get; set; } = default!; + + public string Password { get; set; } = default!; + + public Users() + { + this.Id = Guid.NewGuid(); + } + + public int TenantId { get; set; } + + public string Environment { get; set; } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs new file mode 100644 index 000000000..1b09bab36 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs @@ -0,0 +1,43 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests +{ + [TestClass] + public class EdgeDriverTest + { + private IServiceCollection _services; + + [TestInitialize] + public void Initialize() + { + var configurationRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .Build(); + _services = new ServiceCollection(); + _services.AddSingleton(configurationRoot); + _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW(dbOptions => dbOptions.UseSqlite(), + isolationBuilder => isolationBuilder.SetTenantKey("tenant").SetEnvironmentKey("env").UseMultiTenancy().UseEnvironment())); + System.Environment.SetEnvironmentVariable("env","pro"); + } + + [TestMethod] + public async Task TestTenancyAsync() + { + var serviceProvider = _services.BuildServiceProvider(); + + #region Manually assign values to tenants and environments, and in real scenarios, automatically parse and assign values based on the current HttpContext + + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext(); + httpContextAccessor.HttpContext.Items = new Dictionary() + { + { "tenant", "2" } + }; + + #endregion + + var registerUserEvent = new RegisterUserEvent("jim", "123456"); + var eventBus = serviceProvider.GetRequiredService(); + await eventBus.PublishAsync(registerUserEvent); + } + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs new file mode 100644 index 000000000..7bb991e8f --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs @@ -0,0 +1,51 @@ +using Masa.BuildingBlocks.Isolation.Environment; + +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.EventHandlers; + +public class RegisterUserEventHandler +{ + private readonly CustomDbContext _customDbContext; + private readonly IDataFilter _dataFilter; + private readonly IEnvironmentSetter _environmentSetter; + private readonly IEnvironmentContext _environmentContext; + + public RegisterUserEventHandler(CustomDbContext customDbContext, IDataFilter dataFilter, IEnvironmentSetter environmentSetter, + IEnvironmentContext environmentContext) + { + _customDbContext = customDbContext; + _dataFilter = dataFilter; + _environmentSetter = environmentSetter; + _environmentContext = environmentContext; + } + + [EventHandler] + public async Task RegisterUserAsync(RegisterUserEvent @event) + { + await _customDbContext.Database.EnsureCreatedAsync(); + Assert.IsTrue(_customDbContext.Database.GetConnectionString() == "data source=test3"); + var user = new Users() + { + Account = @event.Account, + Password = MD5Utils.Encrypt(@event.Password, @event.Password) + }; + await _customDbContext.Set().AddAsync(user); + await _customDbContext.SaveChangesAsync(); + + var user2 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsTrue(user2!.Account == @event.Account); + Assert.IsTrue(user2.Environment == "pro"); + Assert.IsTrue(user2.TenantId == 2); + + _environmentSetter.SetEnvironment("dev"); //In EventHandler, physical isolation is not retriggered if a new DbContext is not recreated, it can only be used to filter changes + Assert.IsTrue(_environmentContext.CurrentEnvironment == "dev"); + + var user3 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsNull(user3); + + using (_dataFilter.Disable()) + { + var user4 = await _customDbContext.Set().FirstOrDefaultAsync(); + Assert.IsNotNull(user4); + } + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/RegisterUserEvent.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/RegisterUserEvent.cs new file mode 100644 index 000000000..c01932464 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Events/RegisterUserEvent.cs @@ -0,0 +1,5 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests.Events; + +public record RegisterUserEvent(string Account,string Password) : Event +{ +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj new file mode 100644 index 000000000..f5f1cfdd3 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj @@ -0,0 +1,32 @@ + + + + net6.0 + enable + false + enable + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/TestBase.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/TestBase.cs new file mode 100644 index 000000000..53d30e7b3 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/TestBase.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests; + +public class TestBase : IDisposable +{ + protected readonly string _connectionString = "DataSource=:memory:"; + protected readonly SqliteConnection Connection; + + protected TestBase() + { + Connection = new SqliteConnection(_connectionString); + Connection.Open(); + } + + public void Dispose() + { + Connection.Close(); + } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs new file mode 100644 index 000000000..760bdb8b1 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs @@ -0,0 +1,21 @@ +global using Masa.BuildingBlocks.Dispatcher.Events; +global using Masa.BuildingBlocks.Isolation; +global using Masa.Contrib.Dispatcher.Events; +global using Masa.Contrib.Isolation.Environment; +global using Masa.Contrib.Isolation.MultiTenancy; +global using Masa.Contrib.Isolation.UoW.EF.Web.Tests.Events; +global using Masa.Utils.Data.EntityFrameworkCore; +global using Masa.Utils.Data.EntityFrameworkCore.Filters; +global using Masa.Utils.Data.EntityFrameworkCore.Sqlite; +global using Masa.Utils.Security.Cryptography; +global using Microsoft.AspNetCore.Http; +global using Microsoft.Data.Sqlite; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.EntityFrameworkCore.Metadata.Builders; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System; +global using System.IO; +global using System.Threading.Tasks; + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/appsettings.json b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/appsettings.json new file mode 100644 index 000000000..460845f83 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/appsettings.json @@ -0,0 +1,18 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "data source=test1", + "Isolations": [ + { + "TenantId": "1", + "Environment": "dev", + "ConnectionString": "data source=test2", + "Score": 99 + }, + { + "TenantId": "2", + "Environment": "pro", + "ConnectionString": "data source=test3" + } + ] + } +} \ No newline at end of file From e6b08ccc302618a143a717975ff466074afd4efd Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 23:02:21 +0800 Subject: [PATCH 04/14] chore: remove invalid references --- .../Masa.Contrib.Data.UoW.EF.Tests.csproj | 1 - test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj index 1a9474e95..77fdd7472 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj @@ -13,7 +13,6 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs index 76de8a730..3661d318f 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs @@ -5,9 +5,9 @@ global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Metadata.Builders; +global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; global using System; global using System.Threading.Tasks; -global using Microsoft.Extensions.Configuration; From 982c91580a094a0abb19695d94ee518fbd3c1976 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 14:08:06 +0800 Subject: [PATCH 05/14] test(Isolation): Completing unit tests --- Masa.Contrib.sln | 31 +- .../TenancySaveChangesFilter.cs | 15 +- .../TenantContext.cs | 2 +- .../IsolationDbContext.cs | 15 +- .../Middleware/TenancyMiddleware.cs | 2 +- .../CookieTenantParserProvider.cs | 6 +- .../MultiTenancy/FormTenantParserProvider.cs | 2 +- .../HeaderTenantParserProvider.cs | 2 +- .../HttpContextItemTenantParserProvider.cs | 2 +- .../QueryStringTenantParserProvider.cs | 2 +- .../MultiTenancy/RouteTenantParserProvider.cs | 2 +- .../TestUnitOfWork.cs | 2 +- .../AssemblyResolutionTests.cs | 4 +- .../IntegrationEventBusTest.cs | 2 +- .../IntegrationEventLogServiceTest.cs | 8 +- .../RequestCookieCollection.cs | 6 + .../TestIsolation.cs | 580 +++++++++++++++++- .../_Imports.cs | 1 + 18 files changed, 599 insertions(+), 85 deletions(-) create mode 100644 test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs diff --git a/Masa.Contrib.sln b/Masa.Contrib.sln index f282ed5b7..98a8108c6 100644 --- a/Masa.Contrib.sln +++ b/Masa.Contrib.sln @@ -130,27 +130,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.SearchE EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Service.MinimalAPIs", "src\BuildingBlocks\MASA.BuildingBlocks\src\Service\Masa.BuildingBlocks.Service.MinimalAPIs\Masa.BuildingBlocks.Service.MinimalAPIs.csproj", "{E72E105D-B15F-4D69-9A13-CAA49D4889D6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests", "test\Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests\Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests.csproj", "{4A052E17-4D9E-41EF-89A5-73B917053F8E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests", "test\Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests\Masa.Contrib.Dispatcher.Events.HandlerOrder.Tests.csproj", "{4A052E17-4D9E-41EF-89A5-73B917053F8E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.SearchEngine.AutoComplete", "src\SearchEngine\Masa.Contrib.SearchEngine.AutoComplete\Masa.Contrib.SearchEngine.AutoComplete.csproj", "{3F8532EF-3DC9-45F8-9562-994ABE066585}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.SearchEngine.AutoComplete", "src\SearchEngine\Masa.Contrib.SearchEngine.AutoComplete\Masa.Contrib.SearchEngine.AutoComplete.csproj", "{3F8532EF-3DC9-45F8-9562-994ABE066585}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.SearchEngine.AutoComplete.Tests", "test\Masa.Contrib.SearchEngine.AutoComplete.Tests\Masa.Contrib.SearchEngine.AutoComplete.Tests.csproj", "{31262D61-26A4-4302-968D-52B8DA4558CD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.SearchEngine.AutoComplete.Tests", "test\Masa.Contrib.SearchEngine.AutoComplete.Tests\Masa.Contrib.SearchEngine.AutoComplete.Tests.csproj", "{31262D61-26A4-4302-968D-52B8DA4558CD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.Isolation", "src\BuildingBlocks\MASA.BuildingBlocks\src\Isolation\Masa.BuildingBlocks.Isolation\Masa.BuildingBlocks.Isolation.csproj", "{B689E82B-B3E8-4C83-B56C-D4C27206AAC6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Isolation", "src\BuildingBlocks\MASA.BuildingBlocks\src\Isolation\Masa.BuildingBlocks.Isolation\Masa.BuildingBlocks.Isolation.csproj", "{B689E82B-B3E8-4C83-B56C-D4C27206AAC6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{0C25062D-60A5-4690-974A-CDA9619866B4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{0C25062D-60A5-4690-974A-CDA9619866B4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.MultiTenancy", "src\Isolation\Masa.Contrib.Isolation.MultiTenancy\Masa.Contrib.Isolation.MultiTenancy.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.MultiTenancy", "src\Isolation\Masa.Contrib.Isolation.MultiTenancy\Masa.Contrib.Isolation.MultiTenancy.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.Environment", "src\Isolation\Masa.Contrib.Isolation.Environment\Masa.Contrib.Isolation.Environment.csproj", "{E9A3C62C-EA85-40C3-BB3B-7836D0337F63}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.Environment", "src\Isolation\Masa.Contrib.Isolation.Environment\Masa.Contrib.Isolation.Environment.csproj", "{E9A3C62C-EA85-40C3-BB3B-7836D0337F63}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "temp", "temp", "{03E3863B-452A-4023-BE34-FC84BD5AB04D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Tests\Masa.Contrib.Isolation.UoW.EF.Tests.csproj", "{50551732-AAE0-4F77-B401-9DD02E226CDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication1", "temp\WebApplication1\WebApplication1.csproj", "{B0CF95DC-C094-47A3-B904-475857DA4A4B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Tests\Masa.Contrib.Isolation.UoW.EF.Tests.csproj", "{50551732-AAE0-4F77-B401-9DD02E226CDB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Isolation.UoW.EF.Web.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Web.Tests\Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj", "{7CF33D08-9468-464D-B52E-955A5667EE42}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF.Web.Tests", "test\Masa.Contrib.Isolation.UoW.EF.Web.Tests\Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj", "{7CF33D08-9468-464D-B52E-955A5667EE42}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -552,14 +548,6 @@ Global {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|Any CPU.Build.0 = Release|Any CPU {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.ActiveCfg = Release|Any CPU {E9A3C62C-EA85-40C3-BB3B-7836D0337F63}.Release|x64.Build.0 = Release|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|x64.ActiveCfg = Debug|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Debug|x64.Build.0 = Debug|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|Any CPU.Build.0 = Release|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|x64.ActiveCfg = Release|Any CPU - {B0CF95DC-C094-47A3-B904-475857DA4A4B}.Release|x64.Build.0 = Release|Any CPU {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|Any CPU.Build.0 = Debug|Any CPU {50551732-AAE0-4F77-B401-9DD02E226CDB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -647,7 +635,6 @@ Global {0C25062D-60A5-4690-974A-CDA9619866B4} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} {6D9A3F62-8AB6-40AD-B32F-2A435A9D3341} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} {E9A3C62C-EA85-40C3-BB3B-7836D0337F63} = {022D6FF5-4B65-4213-9A97-C69E2B2F99E1} - {B0CF95DC-C094-47A3-B904-475857DA4A4B} = {03E3863B-452A-4023-BE34-FC84BD5AB04D} {50551732-AAE0-4F77-B401-9DD02E226CDB} = {38E6C400-90C0-493E-9266-C1602E229F1B} {7CF33D08-9468-464D-B52E-955A5667EE42} = {38E6C400-90C0-493E-9266-C1602E229F1B} EndGlobalSection diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs index c00eedbf5..00425e646 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs @@ -5,7 +5,7 @@ public class TenancySaveChangesFilter : ISaveChangesFilter where TKey : IC private readonly ITenantContext _tenantContext; private readonly IConvertProvider _convertProvider; - public TenancySaveChangesFilter(ITenantContext tenantContext,IConvertProvider convertProvider) + public TenancySaveChangesFilter(ITenantContext tenantContext, IConvertProvider convertProvider) { _tenantContext = tenantContext; _convertProvider = convertProvider; @@ -16,10 +16,17 @@ public void OnExecuting(ChangeTracker changeTracker) changeTracker.DetectChanges(); foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) { - if (entity.Entity is IMultiTenant && _tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + if (entity.Entity is IMultiTenant) { - object tenantId = _convertProvider.ChangeType(_tenantContext.CurrentTenant.Id, typeof(TKey)); - entity.CurrentValues[nameof(IMultiTenant.TenantId)] = tenantId; + if (_tenantContext.CurrentTenant != null && !string.IsNullOrEmpty(_tenantContext.CurrentTenant.Id)) + { + object tenantId = _convertProvider.ChangeType(_tenantContext.CurrentTenant.Id, typeof(TKey)); + entity.CurrentValues[nameof(IMultiTenant.TenantId)] = tenantId; + } + else + { + entity.CurrentValues[nameof(IMultiTenant.TenantId)] = default(TKey); + } } } } diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs index 972eec77a..0338ddfb5 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs @@ -4,5 +4,5 @@ public class TenantContext : ITenantContext, ITenantSetter { public Tenant? CurrentTenant { get; private set; } - public void SetTenant(Tenant tenant) => CurrentTenant = tenant; + public void SetTenant(Tenant? tenant) => CurrentTenant = tenant; } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs index 4ba5f92ee..be99876bb 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF; +namespace Masa.Contrib.Isolation.UoW.EF; public abstract class IsolationDbContext : IsolationDbContext { @@ -16,13 +16,11 @@ public abstract class IsolationDbContext : MasaDbContext { private readonly IEnvironmentContext? _environmentContext; private readonly ITenantContext? _tenantContext; - private readonly ILogger>? _logger; public IsolationDbContext(MasaDbContextOptions options) : base(options) { _environmentContext = options.ServiceProvider.GetService(); _tenantContext = options.ServiceProvider.GetService(); - _logger = options.ServiceProvider.GetService>>(); } protected override Expression>? CreateFilterExpression() @@ -33,21 +31,16 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) if (typeof(IMultiTenant<>).IsGenericInterfaceAssignableFrom(typeof(TEntity)) && _tenantContext != null) { Expression> tenantFilter = entity => !IsTenantFilterEnabled || - _tenantContext.CurrentTenant == null || - _tenantContext.CurrentTenant.Id == string.Empty || - _tenantContext.CurrentTenant.Id == null! || - Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)).ToString() == - (_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : default(TKey)!.ToString()); + Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiTenant.TenantId)) + .Equals(_tenantContext.CurrentTenant != null ? _tenantContext.CurrentTenant.Id : default(TKey)); expression = tenantFilter.And(expression != null, expression); } if (typeof(IEnvironment).IsAssignableFrom(typeof(TEntity)) && _environmentContext != null) { Expression> envFilter = entity => !IsEnvironmentFilterEnabled || - _environmentContext.CurrentEnvironment == string.Empty || - _environmentContext.CurrentEnvironment == null! || Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IEnvironment.Environment)) - .Equals(_environmentContext == null ? default : _environmentContext.CurrentEnvironment); + .Equals(_environmentContext != null ? _environmentContext.CurrentEnvironment : default); expression = envFilter.And(expression != null, expression); } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs index 588033ce9..16407e07e 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs @@ -30,7 +30,7 @@ public async Task HandleAsync() foreach (var tenantParserProvider in _tenantParserProviders) { parsers.Add(tenantParserProvider.Name); - if (await tenantParserProvider.ExecuteAsync(_serviceProvider)) + if (await tenantParserProvider.ResolveAsync(_serviceProvider)) { _logger?.LogDebug($"The tenant is successfully resolved, and the resolver is: {string.Join("、 ", parsers)}"); _handled = true; diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs index e3f15a8d3..41fd65b9c 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs @@ -1,10 +1,10 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; public class CookieTenantParserProvider : ITenantParserProvider { - public string Name => "Route"; + public string Name => "Cookie"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs index 61013d850..8dbec6bbf 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs @@ -4,7 +4,7 @@ public class FormTenantParserProvider : ITenantParserProvider { public string Name => "Form"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs index 950e8c22b..576b7fbb9 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs @@ -4,7 +4,7 @@ public class HeaderTenantParserProvider : ITenantParserProvider { public string Name => "Header"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs index b624a8f8f..c437b0608 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs @@ -4,7 +4,7 @@ public class HttpContextItemTenantParserProvider : ITenantParserProvider { public string Name => "Items"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs index 27c5d6f51..75792a9bb 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs @@ -4,7 +4,7 @@ public class QueryStringTenantParserProvider : ITenantParserProvider { public string Name => "QueryString"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs index 2024b8007..a29dfd924 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs @@ -4,7 +4,7 @@ public class RouteTenantParserProvider : ITenantParserProvider { public string Name => "Route"; - public Task ExecuteAsync(IServiceProvider serviceProvider) + public Task ResolveAsync(IServiceProvider serviceProvider) { var httpContext = serviceProvider.GetService()?.HttpContext; var tenantSetter = serviceProvider.GetRequiredService(); diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index cd8a3b4eb..0163015ca 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -160,7 +160,7 @@ public void TestDataConnectionString() } [TestMethod] - public async Task TestUnitOfWorkManagerAsync() + public void TestUnitOfWorkManager() { _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); diff --git a/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs b/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs index cb70a8628..ee0096135 100644 --- a/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs +++ b/test/Masa.Contrib.Dispatcher.Events.Tests/AssemblyResolutionTests.cs @@ -23,7 +23,7 @@ public void TestAddNullAssembly() services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); Assert.ThrowsException(() => { - Assembly[] assemblies = null; + Assembly[] assemblies = null!; services.AddEventBus(assemblies!); }); } @@ -46,7 +46,7 @@ public void TestEventBusByAddNullAssembly() services.AddLogging(loggingBuilder => loggingBuilder.AddConsole()); Assert.ThrowsException(() => { - services.AddTestEventBus(null, ServiceLifetime.Scoped); + services.AddTestEventBus(null!, ServiceLifetime.Scoped); }); } diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs index 0e19ec7d0..9a43b4761 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/IntegrationEventBusTest.cs @@ -117,7 +117,7 @@ public void TestUseLogger() [TestMethod] public void TestAddDaprEventBusAndNullServicesAsync() { - IServiceCollection services = null; + IServiceCollection services = null!; Mock distributedDispatcherOptions = new(); distributedDispatcherOptions.Setup(option => option.Services).Returns(services).Verifiable(); distributedDispatcherOptions.Setup(option => option.Assemblies).Returns(AppDomain.CurrentDomain.GetAssemblies()).Verifiable(); diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs index a0d18f677..94694a347 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.EventLogs.EF.Tests/IntegrationEventLogServiceTest.cs @@ -27,7 +27,7 @@ public async Task TestNullDbTransactionAsync() [TestMethod] public void TestNullServices() { - var dispatcherOptions = CreateDispatcherOptions(null); + var dispatcherOptions = CreateDispatcherOptions(null!); Assert.ThrowsException(() => { @@ -84,10 +84,10 @@ await response.CustomDbContext.Set().AddAsync(new Integrati #endregion var logService = response.ServiceProvider.GetRequiredService(); - var list = await logService.RetrieveEventLogsFailedToPublishAsync(); - Assert.IsTrue(list.Count() == 1); + var list = (await logService.RetrieveEventLogsFailedToPublishAsync()).ToList(); + Assert.IsTrue(list.Count == 1); - var eventLog = list.Select(log => log.Event).FirstOrDefault(); + var eventLog = list.Select(log => log.Event).FirstOrDefault()!; Assert.IsTrue(eventLog.Equals(@event)); } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs new file mode 100644 index 000000000..1b1fc6498 --- /dev/null +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Isolation.UoW.EF.Tests; + +public class RequestCookieCollection : Dictionary, IRequestCookieCollection +{ + public ICollection Keys { get; } +} diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index 1fd9b4f0e..9277ab900 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -1,4 +1,10 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Tests; +using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; +using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Primitives; + +namespace Masa.Contrib.Isolation.UoW.EF.Tests; [TestClass] public class TestIsolation : TestBase @@ -128,7 +134,7 @@ public void TestUseIsolation() var unitOfWorkAccessor = serviceProvider.GetRequiredService(); var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; Assert.IsNotNull(currentDbContextOptions); - Assert.IsTrue(currentDbContextOptions.ConnectionString == "test1"); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "data source=test1"); var unitOfWorkManager = serviceProvider.GetRequiredService(); var unifOfWorkNew = unitOfWorkManager.CreateDbContext(true); @@ -149,31 +155,31 @@ public void TestUseIsolation() var dbContext = unifOfWorkNew.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext) == "test1" && - unitOfWorkAccessorNew.CurrentDbContextOptions!.ConnectionString == "test1"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext) == "data source=test1" && + unitOfWorkAccessorNew.CurrentDbContextOptions!.ConnectionString == "data source=test1"); var unifOfWorkNew2 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("development"); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test1" && - unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test1"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test1" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test1"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("development"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test2" && - unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test2"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test2" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test2"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetTenant(new Tenant("00000000-0000-0000-0000-000000000002")); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("production"); var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test3" && - unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test3"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test3" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test3"); } [TestMethod] @@ -181,17 +187,17 @@ public void TestUseEnvironment() { _services.Configure(option => { - option.DefaultConnection = "test4"; + option.DefaultConnection = "data source=test4"; option.Isolations = new List() { new() { - ConnectionString = "test5", + ConnectionString = "data source=test5", Environment = "dev" }, new() { - ConnectionString = "test6", + ConnectionString = "data source=test6", Environment = "pro" } }; @@ -205,7 +211,7 @@ public void TestUseEnvironment() var unitOfWorkAccessor = serviceProvider.GetRequiredService(); var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; Assert.IsNotNull(currentDbContextOptions); - Assert.IsTrue(currentDbContextOptions.ConnectionString == "test4"); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "data source=test4"); var unitOfWorkManager = serviceProvider.GetRequiredService(); @@ -213,22 +219,22 @@ public void TestUseEnvironment() var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetEnvironment("dev"); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test5" && - unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test5"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test5" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test5"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetEnvironment("pro"); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test6" && - unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test6"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test6" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test6"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetEnvironment("staging"); var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test4" && - unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test4"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test4" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test4"); } [TestMethod] @@ -236,17 +242,17 @@ public void TestUseMultiTenancy() { _services.Configure(option => { - option.DefaultConnection = "test7"; + option.DefaultConnection = "data source=test7"; option.Isolations = new List() { new() { - ConnectionString = "test8", + ConnectionString = "data source=test8", TenantId = "1" }, new() { - ConnectionString = "test9", + ConnectionString = "data source=test9", TenantId = "2" } }; @@ -260,7 +266,7 @@ public void TestUseMultiTenancy() var unitOfWorkAccessor = serviceProvider.GetRequiredService(); var currentDbContextOptions = unitOfWorkAccessor.CurrentDbContextOptions; Assert.IsNotNull(currentDbContextOptions); - Assert.IsTrue(currentDbContextOptions.ConnectionString == "test7"); + Assert.IsTrue(currentDbContextOptions.ConnectionString == "data source=test7"); var unitOfWorkManager = serviceProvider.GetRequiredService(); @@ -268,22 +274,22 @@ public void TestUseMultiTenancy() var unitOfWorkAccessorNew2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); unifOfWorkNew2.ServiceProvider.GetRequiredService().SetTenant(new Tenant("1")); var dbContext2 = unifOfWorkNew2.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "test8" && - unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "test8"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext2) == "data source=test8" && + unitOfWorkAccessorNew2.CurrentDbContextOptions!.ConnectionString == "data source=test8"); var unifOfWorkNew3 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); unifOfWorkNew3.ServiceProvider.GetRequiredService().SetTenant(new Tenant("2")); var dbContext3 = unifOfWorkNew3.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "test9" && - unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "test9"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext3) == "data source=test9" && + unitOfWorkAccessorNew3.CurrentDbContextOptions!.ConnectionString == "data source=test9"); var unifOfWorkNew4 = unitOfWorkManager.CreateDbContext(true); var unitOfWorkAccessorNew4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); unifOfWorkNew4.ServiceProvider.GetRequiredService().SetTenant(null!); var dbContext4 = unifOfWorkNew4.ServiceProvider.GetRequiredService(); - Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "test7" && - unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "test7"); + Assert.IsTrue(GetDataBaseConnectionString(dbContext4) == "data source=test7" && + unitOfWorkAccessorNew4.CurrentDbContextOptions!.ConnectionString == "data source=test7"); } [TestMethod] @@ -302,5 +308,519 @@ public void TestIsolationBuilder() Assert.IsTrue(isolationBuilder.SetTenantParsers(new List()).EnvironmentParsers.Count == 0); } + [TestMethod] + public async Task TestCookieTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Cookies = new RequestCookieCollection + { + { + tenantKey, "1" + } + } + } + }; + var provider = new CookieTenantParserProvider(); + Assert.IsTrue(provider.Name == "Cookie"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestCookieTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Cookies = new RequestCookieCollection() + } + }; + var provider = new CookieTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestCookieTenantParser3Async() + { + var services = new ServiceCollection(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var provider = new CookieTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestFormTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Form = new FormCollection(new Dictionary() + { + { tenantKey, "1" } + } + ) + } + }; + var provider = new FormTenantParserProvider(); + Assert.IsTrue(provider.Name == "Form"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestFormTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Form = new FormCollection(new Dictionary()) + } + }; + var provider = new FormTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestFormTenantParser3Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + QueryString = QueryString.Create(tenantKey, "1") + } + }; + var provider = new FormTenantParserProvider(); + Assert.IsTrue(provider.Name == "Form"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestHeaderTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Headers = + { + { tenantKey, "1" } + } + } + }; + var provider = new HeaderTenantParserProvider(); + Assert.IsTrue(provider.Name == "Header"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestHeaderTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + Headers = { } + } + }; + var provider = new HeaderTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestHttpContextItemTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Items = new Dictionary() + { + { tenantKey, "1" } + } + }; + var provider = new HttpContextItemTenantParserProvider(); + Assert.IsTrue(provider.Name == "Items"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestHttpContextItemTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Items = new Dictionary() + }; + var provider = new HttpContextItemTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestHttpContextItemTenantParser3Async() + { + var services = new ServiceCollection(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var provider = new HttpContextItemTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestQueryStringTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = { QueryString = QueryString.Create(tenantKey, "1") } + }; + var provider = new QueryStringTenantParserProvider(); + Assert.IsTrue(provider.Name == "QueryString"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestQueryStringTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = { QueryString = new QueryString() } + }; + var provider = new QueryStringTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestQueryStringTenantParser3Async() + { + var services = new ServiceCollection(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var provider = new QueryStringTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestRouteTenantParserAsync() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + RouteValues = new RouteValueDictionary() + { + { tenantKey, "1" } + } + } + }; + var provider = new RouteTenantParserProvider(); + Assert.IsTrue(provider.Name == "Route"); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsTrue(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task TestRouteTenantParser2Async() + { + var services = new ServiceCollection(); + services.AddHttpContextAccessor(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext() + { + Request = + { + RouteValues = new RouteValueDictionary() + } + }; + var provider = new RouteTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestRouteTenantParser3Async() + { + var services = new ServiceCollection(); + string tenantKey = "tenant"; + Mock tenantSetter = new(); + tenantSetter.Setup(setter => setter.SetTenant(It.IsAny())).Verifiable(); + services.AddScoped(_ => tenantSetter.Object); + services.Configure(option => + { + option.TenantKey = tenantKey; + }); + var provider = new RouteTenantParserProvider(); + var handler = await provider.ResolveAsync(services.BuildServiceProvider()); + Assert.IsFalse(handler); + tenantSetter.Verify(setter => setter.SetTenant(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task TestEnvironmentVariablesParserAsync() + { + var services = new ServiceCollection(); + Mock environmentSetter = new(); + string environmentKey = "env"; + environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + services.Configure(option => + { + option.EnvironmentKey = environmentKey; + }); + System.Environment.SetEnvironmentVariable(environmentKey, "dev"); + var serviceProvider = services.BuildServiceProvider(); + var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); + var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider); + Assert.IsTrue(handler); + } + + [TestMethod] + public async Task TestEnvironmentVariablesParser2Async() + { + var services = new ServiceCollection(); + Mock environmentSetter = new(); + string environmentKey = "env"; + environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); + services.AddScoped(_ => environmentSetter.Object); + services.Configure(option => + { + option.EnvironmentKey = environmentKey; + }); + var serviceProvider = services.BuildServiceProvider(); + var environmentVariablesParserProvider = new EnvironmentVariablesParserProvider(); + var handler = await environmentVariablesParserProvider.ResolveAsync(serviceProvider); + Assert.IsFalse(handler); + } + + [TestMethod] + public void TestGetDbContextOptionsList() + { + var services = new ServiceCollection(); + services.Configure(option => + { + option.DefaultConnection = "data source=test2"; + option.Isolations = new() + { + new() + { + Environment = "dev", + ConnectionString = "data source=test3" + }, + new() + { + Environment = "pro", + ConnectionString = "data source=test4" + } + }; + }); + services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); + var provider = serviceProvider.GetRequiredService(); + Assert.IsTrue(provider.DbContextOptionsList.Distinct().Count() == 3); + } + private string GetDataBaseConnectionString(CustomDbContext dbContext) => dbContext.Database.GetConnectionString()!; + } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs index aea137644..6173adc58 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -18,3 +18,4 @@ global using System; global using System.IO; global using System.Linq; +global using Microsoft.AspNetCore.Http; From f90c48a069f2bc56a578d3420789630fd4324563 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 15:20:19 +0800 Subject: [PATCH 06/14] chore(IntegrationEvents.Dapr): Adjust UoW enable check prompt --- .../ServiceCollectionExtensions.cs | 2 +- .../RequestCookieCollection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs index eb585e51b..131da625f 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/ServiceCollectionExtensions.cs @@ -47,7 +47,7 @@ internal static IServiceCollection TryAddDaprEventBus service.ServiceType != typeof(IUnitOfWork))) { var logger = services.BuildServiceProvider().GetService>(); - logger?.LogWarning("UoW is not enabled, local messages will not be integrated"); + logger?.LogDebug("UoW is not enabled or add delay, UoW is not used will affect 100% delivery of the message"); } return services; diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs index 1b1fc6498..16401caf8 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/RequestCookieCollection.cs @@ -2,5 +2,5 @@ public class RequestCookieCollection : Dictionary, IRequestCookieCollection { - public ICollection Keys { get; } + public new ICollection Keys { get; } } From ae7633d584d1201584f0f360cd9a6d14eb342843 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 15:21:47 +0800 Subject: [PATCH 07/14] chore: update masa.utils library package version to 0.4.0-preview2 --- .../Masa.Contrib.Isolation.Environment.csproj | 2 +- .../Masa.Contrib.Isolation.MultiTenancy.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj b/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj index b1f9910a2..9f22c786e 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/Masa.Contrib.Isolation.Environment.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj index 228b479bf..8f2f07b48 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj @@ -11,7 +11,7 @@ - + From d6074f8ca31f58acf1bee24432e0a8de4d58b60c Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 15:37:37 +0800 Subject: [PATCH 08/14] Test(Isolation): adjust Isolation UnitTest --- .../Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs | 9 ++------- test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs | 7 ++++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index 9277ab900..8a9c2189e 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -1,10 +1,4 @@ -using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; -using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Primitives; - -namespace Masa.Contrib.Isolation.UoW.EF.Tests; +namespace Masa.Contrib.Isolation.UoW.EF.Tests; [TestClass] public class TestIsolation : TestBase @@ -782,6 +776,7 @@ public async Task TestEnvironmentVariablesParser2Async() var services = new ServiceCollection(); Mock environmentSetter = new(); string environmentKey = "env"; + System.Environment.SetEnvironmentVariable(environmentKey, ""); environmentSetter.Setup(setter => setter.SetEnvironment(It.IsAny())).Verifiable(); services.AddScoped(_ => environmentSetter.Object); services.Configure(option => diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs index 6173adc58..bcdd1cc22 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -6,16 +6,21 @@ global using Masa.BuildingBlocks.Isolation.Options; global using Masa.Contrib.Isolation.Environment; global using Masa.Contrib.Isolation.MultiTenancy; +global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; +global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; global using Masa.Utils.Data.EntityFrameworkCore; global using Masa.Utils.Data.EntityFrameworkCore.Sqlite; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Routing; global using Microsoft.Data.Sqlite; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Metadata.Builders; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Primitives; global using Microsoft.VisualStudio.TestTools.UnitTesting; global using Moq; global using System; global using System.IO; global using System.Linq; -global using Microsoft.AspNetCore.Http; + From e03697d75a42e0549f5d2975fe1c5167f9f9e346 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 18:57:19 +0800 Subject: [PATCH 09/14] chore(Isolation): Modify the Isolation document, Adjust the IEnvironment class name to IMultiEnvironment --- Masa.Contrib.sln | 2 +- src/BuildingBlocks/MASA.BuildingBlocks | 2 +- .../UnitOfWorkManager.cs | 3 +- .../EnvironmentSaveChangesFilter.cs | 4 +- .../README.md | 8 +-- .../README.zh-CN.md | 8 +-- .../ConvertProvider.cs | 4 +- .../IsolationBuilderExtensions.cs | 8 +-- ...Masa.Contrib.Isolation.MultiTenant.csproj} | 0 .../README.md | 15 +++-- .../README.zh-CN.md | 15 +++-- .../TenantContext.cs | 2 +- .../TenantSaveChangesFilter.cs} | 6 +- .../_Imports.cs | 1 - .../DefaultConnectionStringProvider.cs | 4 +- .../DispatcherOptionsExtensions.cs | 4 +- .../{TypeCommon.cs => TypeExtensions.cs} | 2 +- .../IsolationDbContext.cs | 6 +- ...eware.cs => MultiEnvironmentMiddleware.cs} | 8 +-- ...Middleware.cs => MultiTenantMiddleware.cs} | 8 +-- .../CookieTenantParserProvider.cs | 2 +- .../FormTenantParserProvider.cs | 2 +- .../HeaderTenantParserProvider.cs | 2 +- .../HttpContextItemTenantParserProvider.cs | 2 +- .../QueryStringTenantParserProvider.cs | 2 +- .../RouteTenantParserProvider.cs | 2 +- .../Masa.Contrib.Isolation.UoW.EF/README.md | 18 ++--- .../README.zh-CN.md | 10 +-- .../Masa.Contrib.Isolation.UoW.EF/_Imports.cs | 5 +- .../TestUnitOfWork.cs | 3 +- ...Masa.Contrib.Isolation.UoW.EF.Tests.csproj | 2 +- .../TestIsolation.cs | 15 +++-- .../_Imports.cs | 2 - .../EdgeDriverTest.cs | 67 +++++++++---------- .../EventHandlers/RegisterUserEventHandler.cs | 2 +- ....Contrib.Isolation.UoW.EF.Web.Tests.csproj | 2 +- .../_Imports.cs | 3 +- 37 files changed, 127 insertions(+), 124 deletions(-) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/ConvertProvider.cs (73%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/IsolationBuilderExtensions.cs (59%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj => Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj} (100%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/README.md (84%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/README.zh-CN.md (84%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/TenantContext.cs (79%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs => Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs} (81%) rename src/Isolation/{Masa.Contrib.Isolation.MultiTenancy => Masa.Contrib.Isolation.MultiTenant}/_Imports.cs (99%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/{TypeCommon.cs => TypeExtensions.cs} (91%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/{EnvironmentMiddleware.cs => MultiEnvironmentMiddleware.cs} (80%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/{TenancyMiddleware.cs => MultiTenantMiddleware.cs} (81%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/CookieTenantParserProvider.cs (93%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/FormTenantParserProvider.cs (93%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/HeaderTenantParserProvider.cs (93%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/HttpContextItemTenantParserProvider.cs (93%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/QueryStringTenantParserProvider.cs (93%) rename src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/{MultiTenancy => MultiTenant}/RouteTenantParserProvider.cs (92%) diff --git a/Masa.Contrib.sln b/Masa.Contrib.sln index 98a8108c6..67e9e4980 100644 --- a/Masa.Contrib.sln +++ b/Masa.Contrib.sln @@ -140,7 +140,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.BuildingBlocks.Isolati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.UoW.EF", "src\Isolation\Masa.Contrib.Isolation.UoW.EF\Masa.Contrib.Isolation.UoW.EF.csproj", "{0C25062D-60A5-4690-974A-CDA9619866B4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.MultiTenancy", "src\Isolation\Masa.Contrib.Isolation.MultiTenancy\Masa.Contrib.Isolation.MultiTenancy.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.MultiTenant", "src\Isolation\Masa.Contrib.Isolation.MultiTenant\Masa.Contrib.Isolation.MultiTenant.csproj", "{6D9A3F62-8AB6-40AD-B32F-2A435A9D3341}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Contrib.Isolation.Environment", "src\Isolation\Masa.Contrib.Isolation.Environment\Masa.Contrib.Isolation.Environment.csproj", "{E9A3C62C-EA85-40C3-BB3B-7836D0337F63}" EndProject diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index ed8277ca0..0a6e41874 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit ed8277ca02b1259277836f34568719bd37bf705a +Subproject commit 0a6e41874a315d2d46ede9873ae5d674ffbb409d diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs index 7601de4d7..9376ba11e 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs @@ -7,7 +7,8 @@ public class UnitOfWorkManager : IUnitOfWorkManager where TDbContext public UnitOfWorkManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; /// - /// + /// Create new DbContext + /// We create DbContext with lazy loading enabled by default /// /// Deferred creation of DbContext, easy to specify tenant or environment by yourself, which is very effective for physical isolation /// diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs index 548df8976..10adea2b5 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/EnvironmentSaveChangesFilter.cs @@ -14,9 +14,9 @@ public void OnExecuting(ChangeTracker changeTracker) changeTracker.DetectChanges(); foreach (var entity in changeTracker.Entries().Where(entry => entry.State == EntityState.Added)) { - if (entity.Entity is IEnvironment) + if (entity.Entity is IMultiEnvironment) { - entity.CurrentValues[nameof(IEnvironment.Environment)] = _environmentContext.CurrentEnvironment; + entity.CurrentValues[nameof(IMultiEnvironment.Environment)] = _environmentContext.CurrentEnvironment; } } } diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md index d30933e75..54ff35309 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md @@ -6,7 +6,7 @@ Example: ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.Environment // Environmental isolation Quote on demand +Install-Package Masa.Contrib.Isolation.Environment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -38,14 +38,14 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment());// Use environment isolation + isolationBuilder => isolationBuilder.UseEnvironment()); }); ```` 3. DbContext needs to inherit IsolationDbContext ```` C# -public class CustomDbContext : MasaDbContext +public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { @@ -55,7 +55,7 @@ public class CustomDbContext : MasaDbContext 4. The class corresponding to the isolated table needs to implement IEnvironment -You can also choose not to implement IMultiTenant when using physical isolation +You can also choose not to implement IEnvironment when using physical isolation ##### Summarize diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md index c1f8f45c1..1197c3934 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md @@ -6,7 +6,7 @@ ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.Environment // 环境隔离 按需引用 +Install-Package Masa.Contrib.Isolation.Environment Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -39,14 +39,14 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment());// 使用环境隔离 + isolationBuilder => isolationBuilder.UseEnvironment()); }); ``` 3. DbContext需要继承IsolationDbContext ``` C# -public class CustomDbContext : MasaDbContext +public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { @@ -56,7 +56,7 @@ public class CustomDbContext : MasaDbContext 4. 隔离的表对应的类需要实现IEnvironment -采用物理隔离时也可以选择不实现IMultiTenant +采用物理隔离时也可以选择不实现IEnvironment ##### 总结 diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs similarity index 73% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs index 004f8362d..1594b596f 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/ConvertProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/ConvertProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.MultiTenancy; +namespace Masa.Contrib.Isolation.MultiTenant; public class ConvertProvider : IConvertProvider { @@ -11,7 +11,7 @@ public object ChangeType(string value, Type conversionType) } else { - result= Convert.ChangeType(value, conversionType); + result = Convert.ChangeType(value, conversionType); } return result; } diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs similarity index 59% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs index 50f08a6ea..ef6af4290 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/IsolationBuilderExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/IsolationBuilderExtensions.cs @@ -1,13 +1,13 @@ -namespace Masa.Contrib.Isolation.MultiTenancy; +namespace Masa.Contrib.Isolation.MultiTenant; public static class IsolationBuilderExtensions { - public static IIsolationBuilder UseMultiTenancy(this IIsolationBuilder isolationBuilder) => isolationBuilder.UseMultiTenancy(); + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder) => isolationBuilder.UseMultiTenant(); - public static IIsolationBuilder UseMultiTenancy(this IIsolationBuilder isolationBuilder) where TKey : IComparable + public static IIsolationBuilder UseMultiTenant(this IIsolationBuilder isolationBuilder) where TKey : IComparable { isolationBuilder.Services.TryAddSingleton(); - isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(TenancySaveChangesFilter), ServiceLifetime.Scoped)); + isolationBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(ISaveChangesFilter), typeof(TenantSaveChangesFilter), ServiceLifetime.Scoped)); isolationBuilder.Services.TryAddScoped(); isolationBuilder.Services.TryAddScoped(typeof(ITenantContext), serviceProvider => serviceProvider.GetRequiredService()); isolationBuilder.Services.TryAddScoped(typeof(ITenantSetter), serviceProvider => serviceProvider.GetRequiredService()); diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj similarity index 100% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/Masa.Contrib.Isolation.MultiTenancy.csproj rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/Masa.Contrib.Isolation.MultiTenant.csproj diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md similarity index 84% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index c3d814a1c..4b6b85f52 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -1,12 +1,12 @@ [中](README.zh-CN.md) | EN -## Masa.Contrib.Isolation.MultiTenancy +## Masa.Contrib.Isolation.MultiTenant Example: ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.MultiTenancy // Multi-tenant isolation On-demand reference +Install-Package Masa.Contrib.Isolation.MultiTenant // Multi-tenant isolation On-demand reference Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -31,6 +31,7 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer * 1.1 When the current tenant is 00000000-0000-0000-0000-000000000002: database address: server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; * 1.2 When the current tenant is 00000000-0000-0000-0000-000000000003: database address: server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 Other tenants: database address: server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; 2. Using Isolation.UoW.EF ```` C# @@ -38,14 +39,14 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseMultiTenancy());// Use tenant isolation + isolationBuilder => isolationBuilder.UseMultiTenant());// Use tenant isolation }); ```` 3. DbContext needs to inherit IsolationDbContext ```` C# -public class CustomDbContext : MasaDbContext +public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { @@ -58,7 +59,7 @@ public class CustomDbContext : MasaDbContext You can also choose not to implement IMultiTenant when using physical isolation > The tenant id type can be specified by yourself, the default Guid type -> * For example: Implement IMultiTenant to implement IMultiTenant, UseMultiTenancy() to UseMultiTenancy() +> * For example: Implement IMultiTenant to implement IMultiTenant, UseMultiTenant() to UseMultiTenant() ##### Summarize @@ -73,7 +74,7 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenancy());// Use tenant isolation + isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// Use tenant isolation }); ```` * How to change the parser @@ -86,6 +87,6 @@ builder.Services.AddEventBus(eventBusBuilder => isolationBuilder => isolationBuilder.SetTenantParsers(new List() { new QueryStringTenantParserProvider() - }).UseMultiTenancy());// Use tenant isolation + }).UseMultiTenant());// Use tenant isolation }); ```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md similarity index 84% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index e3f67ffac..1d4ef1107 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -1,12 +1,12 @@ 中 | [EN](README.md) -## Masa.Contrib.Isolation.MultiTenancy +## Masa.Contrib.Isolation.MultiTenant 用例: ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.MultiTenancy // 多租户隔离 按需引用 +Install-Package Masa.Contrib.Isolation.MultiTenant // 多租户隔离 按需引用 Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -31,6 +31,7 @@ Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer * 1.1 当前租户为00000000-0000-0000-0000-000000000002时:数据库地址:server=localhost,1674;uid=sa;pwd=P@ssw0rd;database=identity; * 1.2 当前租户为00000000-0000-0000-0000-000000000003时:数据库地址:server=localhost,1672;uid=sa;pwd=P@ssw0rd;database=identity; +* 1.3 其他租户:数据库地址:server=localhost;uid=sa;pwd=P@ssw0rd;database=identity; 2. 使用Isolation.UoW.EF ``` C# @@ -38,14 +39,14 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseMultiTenancy());// 使用租户隔离 + isolationBuilder => isolationBuilder.UseMultiTenant());// 使用租户隔离 }); ``` 3. DbContext需要继承IsolationDbContext ``` C# -public class CustomDbContext : MasaDbContext +public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { @@ -58,7 +59,7 @@ public class CustomDbContext : MasaDbContext 采用物理隔离时也可以选择不实现IMultiTenant > 租户id类型支持自行指定,默认Guid类型 -> * 如:实现IMultiTenant改为实现IMultiTenant,UseMultiTenancy()改为UseMultiTenancy() +> * 如:实现IMultiTenant改为实现IMultiTenant,UseMultiTenant()改为UseMultiTenant() ##### 总结 @@ -73,7 +74,7 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenancy());// 使用租户隔离 + isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// 使用租户隔离 }); ``` * 如何更改解析器 @@ -86,6 +87,6 @@ builder.Services.AddEventBus(eventBusBuilder => isolationBuilder => isolationBuilder.SetTenantParsers(new List() { new QueryStringTenantParserProvider() - }).UseMultiTenancy());// 使用租户隔离 + }).UseMultiTenant());// 使用租户隔离 }); ``` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs similarity index 79% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs index 0338ddfb5..595e64289 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenantContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantContext.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.MultiTenancy; +namespace Masa.Contrib.Isolation.MultiTenant; public class TenantContext : ITenantContext, ITenantSetter { diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs similarity index 81% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs index 00425e646..c99d996db 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/TenancySaveChangesFilter.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/TenantSaveChangesFilter.cs @@ -1,11 +1,11 @@ -namespace Masa.Contrib.Isolation.MultiTenancy; +namespace Masa.Contrib.Isolation.MultiTenant; -public class TenancySaveChangesFilter : ISaveChangesFilter where TKey : IComparable +public class TenantSaveChangesFilter : ISaveChangesFilter where TKey : IComparable { private readonly ITenantContext _tenantContext; private readonly IConvertProvider _convertProvider; - public TenancySaveChangesFilter(ITenantContext tenantContext, IConvertProvider convertProvider) + public TenantSaveChangesFilter(ITenantContext tenantContext, IConvertProvider convertProvider) { _tenantContext = tenantContext; _convertProvider = convertProvider; diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs similarity index 99% rename from src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs rename to src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs index 10cf11801..7d4068925 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenancy/_Imports.cs +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/_Imports.cs @@ -6,4 +6,3 @@ global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection.Extensions; global using System.Linq; - diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs index c3d4296ab..d7fae1d3a 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs @@ -81,8 +81,8 @@ private string GetMessage() if (_environmentContext != null) stringBuilder.Append($"Environment: [{_environmentContext.CurrentEnvironment}], "); if (_tenantContext != null) - stringBuilder.Append($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}]"); + stringBuilder.Append($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}], "); var message = stringBuilder.ToString(); - return message.Substring(0, message.Length - 1); + return message.Substring(0, message.Length - 2); } } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs index cd9ff2f1f..fb9eb53ac 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DispatcherOptionsExtensions.cs @@ -54,10 +54,10 @@ private static IServiceCollection UseIsolationUoW( services.AddHttpContextAccessor(); if (services.Any(service => service.ServiceType == typeof(ITenantContext))) - services.AddScoped(serviceProvider => new TenancyMiddleware(serviceProvider, builder.TenantParsers)); + services.AddScoped(serviceProvider => new MultiTenantMiddleware(serviceProvider, builder.TenantParsers)); if (services.Any(service => service.ServiceType == typeof(IEnvironmentContext))) - services.AddScoped(serviceProvider => new EnvironmentMiddleware(serviceProvider, builder.EnvironmentParsers)); + services.AddScoped(serviceProvider => new MultiEnvironmentMiddleware(serviceProvider, builder.EnvironmentParsers)); services.AddTransient(typeof(IMiddleware<>), typeof(IsolationMiddleware<>)); services.TryAddSingleton(); diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs similarity index 91% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs index 7a583b561..b09229d28 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeCommon.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Internal/TypeExtensions.cs @@ -1,6 +1,6 @@ namespace Masa.Contrib.Isolation.UoW.EF.Internal; -internal static class TypeCommon +internal static class TypeExtensions { static bool IsConcrete(this Type type) => !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs index be99876bb..595c1e076 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/IsolationDbContext.cs @@ -36,10 +36,10 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) expression = tenantFilter.And(expression != null, expression); } - if (typeof(IEnvironment).IsAssignableFrom(typeof(TEntity)) && _environmentContext != null) + if (typeof(IMultiEnvironment).IsAssignableFrom(typeof(TEntity)) && _environmentContext != null) { Expression> envFilter = entity => !IsEnvironmentFilterEnabled || - Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IEnvironment.Environment)) + Microsoft.EntityFrameworkCore.EF.Property(entity, nameof(IMultiEnvironment.Environment)) .Equals(_environmentContext != null ? _environmentContext.CurrentEnvironment : default); expression = envFilter.And(expression != null, expression); } @@ -51,7 +51,7 @@ public IsolationDbContext(MasaDbContextOptions options) : base(options) return expression; } - protected virtual bool IsEnvironmentFilterEnabled => DataFilter?.IsEnabled() ?? false; + protected virtual bool IsEnvironmentFilterEnabled => DataFilter?.IsEnabled() ?? false; protected virtual bool IsTenantFilterEnabled => DataFilter?.IsEnabled>() ?? false; } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs similarity index 80% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs index 2143a7e67..95aab59ea 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/EnvironmentMiddleware.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiEnvironmentMiddleware.cs @@ -1,17 +1,17 @@ namespace Masa.Contrib.Isolation.UoW.EF.Middleware; -public class EnvironmentMiddleware : IIsolationMiddleware +public class MultiEnvironmentMiddleware : IIsolationMiddleware { private readonly IServiceProvider _serviceProvider; - private readonly ILogger? _logger; + private readonly ILogger? _logger; private readonly IEnvironmentContext _environmentContext; private readonly IEnumerable _environmentParserProviders; private bool _handled; - public EnvironmentMiddleware(IServiceProvider serviceProvider, IEnumerable environmentParserProviders) + public MultiEnvironmentMiddleware(IServiceProvider serviceProvider, IEnumerable environmentParserProviders) { _serviceProvider = serviceProvider; - _logger = _serviceProvider.GetService>(); + _logger = _serviceProvider.GetService>(); _environmentContext = _serviceProvider.GetRequiredService(); _environmentParserProviders = environmentParserProviders; } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs similarity index 81% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs index 16407e07e..737664ca8 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/TenancyMiddleware.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Middleware/MultiTenantMiddleware.cs @@ -1,16 +1,16 @@ namespace Masa.Contrib.Isolation.UoW.EF.Middleware; -public class TenancyMiddleware : IIsolationMiddleware +public class MultiTenantMiddleware : IIsolationMiddleware { private readonly IServiceProvider _serviceProvider; - private readonly ILogger? _logger; + private readonly ILogger? _logger; private readonly ITenantContext _tenantContext; private readonly IEnumerable _tenantParserProviders; private bool _handled; - public TenancyMiddleware(IServiceProvider serviceProvider, IEnumerable tenantParserProviders) + public MultiTenantMiddleware(IServiceProvider serviceProvider, IEnumerable tenantParserProviders) { _serviceProvider = serviceProvider; - _logger = _serviceProvider.GetService>(); + _logger = _serviceProvider.GetService>(); _tenantContext = _serviceProvider.GetRequiredService(); _tenantParserProviders = tenantParserProviders; } diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs similarity index 93% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs index 41fd65b9c..caa46a285 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/CookieTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/CookieTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class CookieTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs similarity index 93% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs index 8dbec6bbf..1858a7208 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/FormTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/FormTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class FormTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs similarity index 93% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs index 576b7fbb9..db129a3f5 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HeaderTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HeaderTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class HeaderTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs similarity index 93% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs index c437b0608..102f56e04 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/HttpContextItemTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/HttpContextItemTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class HttpContextItemTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs similarity index 93% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs index 75792a9bb..9fca23fb0 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/QueryStringTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/QueryStringTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class QueryStringTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs similarity index 92% rename from src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs rename to src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs index a29dfd924..996817fde 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenancy/RouteTenantParserProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/Parser/MultiTenant/RouteTenantParserProvider.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; +namespace Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; public class RouteTenantParserProvider : ITenantParserProvider { diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index 693a90db9..2b1c500ff 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -7,7 +7,7 @@ Example: ```C# Install-Package Masa.Contrib.Isolation.UoW.EF Install-Package Masa.Contrib.Isolation.Environment // Environmental isolation Quote on demand -Install-Package Masa.Contrib.Isolation.MultiTenancy // Multi-tenant isolation On-demand reference +Install-Package Masa.Contrib.Isolation.MultiTenant // Multi-tenant isolation On-demand reference Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -46,24 +46,26 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenancy());// Select usage environment or tenant isolation as needed + isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenant());// Select usage environment or tenant isolation as needed }); ``` -3. optional use: +3. DbContext needs to inherit IsolationDbContext ``` C# -var app = builder.Build(); -app.UseIsolation(); +public class CustomDbContext : IsolationDbContext +{ + public CustomDbContext(MasaDbContextOptions options) : base(options) + { + } +} ``` -> When the DbContext is not operated in the EventHandler, it needs to be used when the DbContext is directly operated in the Controller or Minimal, otherwise the environment and tenant acquisition will fail, resulting in the database address being selected as the default DefaultConnection address - 4. The class corresponding to the isolated table needs to implement IMultiTenant or IEnvironment Tenant isolation implements IMultiTenant, and environment isolation implements IEnvironment ##### Summarize * When are tenants and environments resolved? - * Manipulating DbContext, Repository, etc. in EventHandler can be used directly without adding. The environment and tenant information will be parsed when the Event is published. If the environment or tenant has been assigned, the parsing will be skipped + * Manipulating DbContext, Repository, etc. in EventHandler can be used directly without any additional operations and no increase in workload. When Event is published, the environment and tenant information will be parsed through the provided parser. If the environment or tenant has been assigned, then will skip parsing * To operate DbContext, Repository, etc. directly in the controller or MinimalAPI, you need to add `app.UseIsolation();` in Program.cs, the environment and tenant information will be parsed in the middleware of AspNetCore, if the environment or tenant has been assigned, will skip parsing \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index 1b5f54e48..e477a6d2f 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -7,7 +7,7 @@ ```C# Install-Package Masa.Contrib.Isolation.UoW.EF Install-Package Masa.Contrib.Isolation.Environment // 环境隔离 按需引用 -Install-Package Masa.Contrib.Isolation.MultiTenancy // 多租户隔离 按需引用 +Install-Package Masa.Contrib.Isolation.MultiTenant // 多租户隔离 按需引用 Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` @@ -46,14 +46,14 @@ builder.Services.AddEventBus(eventBusBuilder => { eventBusBuilder.UseIsolationUoW( dbOptions => dbOptions.UseSqlServer(), - isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenancy());// 按需选择使用环境或者租户隔离 + isolationBuilder => isolationBuilder.UseEnvironment().UseMultiTenant());// 按需选择使用环境或者租户隔离 }); ``` 3. DbContext需要继承IsolationDbContext ``` C# -public class CustomDbContext : MasaDbContext +public class CustomDbContext : IsolationDbContext { public CustomDbContext(MasaDbContextOptions options) : base(options) { @@ -67,5 +67,5 @@ public class CustomDbContext : MasaDbContext ##### 总结 * 租户与环境什么时候被解析? - * 在EventHandler中操作DbContext、Repository等可直接使用,无需添加,在Event被Publish会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 - * 直接在控制器或MinimalAPI中操作DbContext、Repository等需要在Program.cs中添加`app.UseIsolation();`,在AspNetCore的中间件中会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 + * 在Event被Publish会通过提供的解析器解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 + * 不使用EventBus,直接在控制器或MinimalAPI中操作DbContext、Repository等需要在Program.cs中添加`app.UseIsolation();`,在AspNetCore的中间件中会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs index 6ab70aec1..956967146 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/_Imports.cs @@ -8,6 +8,8 @@ global using Masa.Contrib.Data.UoW.EF; global using Masa.Contrib.Isolation.UoW.EF.Internal; global using Masa.Contrib.Isolation.UoW.EF.Middleware; +global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; +global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; global using Masa.Utils.Data.EntityFrameworkCore; global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Http; @@ -20,6 +22,3 @@ global using System.Linq.Expressions; global using System.Reflection; global using System.Text; -global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; -global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; - diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index 0163015ca..1ca643d71 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -194,11 +194,12 @@ public async Task TestUnitOfWorkAccessorAsync() Assert.IsTrue(unitOfWorkAccessor is { CurrentDbContextOptions: null }); var unitOfWork = serviceProvider.GetRequiredService(); Assert.IsNotNull(unitOfWork); + Assert.IsTrue(!unitOfWork.TransactionHasBegun); unitOfWorkAccessor = serviceProvider.GetService(); Assert.IsTrue(unitOfWorkAccessor!.CurrentDbContextOptions != null && unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); var unitOfWorkManager = serviceProvider.GetRequiredService(); - var unitOfWorkNew = unitOfWorkManager.CreateDbContext(); + var unitOfWorkNew = unitOfWorkManager.CreateDbContext(false); var unitOfWorkAccessorNew = unitOfWorkNew.ServiceProvider.GetService(); Assert.IsTrue(unitOfWorkAccessorNew!.CurrentDbContextOptions != null && unitOfWorkAccessorNew.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj index 316d914ea..4abf62d6c 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/Masa.Contrib.Isolation.UoW.EF.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs index 8a9c2189e..62f9b5297 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/TestIsolation.cs @@ -1,4 +1,7 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Tests; +using Masa.Contrib.Isolation.MultiTenant; +using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenant; + +namespace Masa.Contrib.Isolation.UoW.EF.Tests; [TestClass] public class TestIsolation : TestBase @@ -91,7 +94,7 @@ public void TestUseIsolationUoWByUseTenant() Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseMultiTenancy()); + isolationBuilder => isolationBuilder.UseMultiTenant()); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsNotNull(serviceProvider.GetService()); @@ -104,7 +107,7 @@ public void TestUseIsolationUoWByUseMultiTenant() Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(_connectionString), - isolationBuilder => isolationBuilder.UseMultiTenancy().UseMultiTenancy()); + isolationBuilder => isolationBuilder.UseMultiTenant().UseMultiTenant()); var serviceProvider = dispatcherOption.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -122,7 +125,7 @@ public void TestUseIsolation() Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), - isolationBuilder => isolationBuilder.UseMultiTenancy().UseEnvironment()); + isolationBuilder => isolationBuilder.UseMultiTenant().UseEnvironment()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); @@ -232,7 +235,7 @@ public void TestUseEnvironment() } [TestMethod] - public void TestUseMultiTenancy() + public void TestUseMultiTenant() { _services.Configure(option => { @@ -254,7 +257,7 @@ public void TestUseMultiTenancy() Mock dispatcherOption = new(); dispatcherOption.Setup(builder => builder.Services).Returns(_services).Verifiable(); dispatcherOption.Object.UseIsolationUoW(dbOptionBuilder => dbOptionBuilder.UseSqlite(), - isolationBuilder => isolationBuilder.UseMultiTenancy()); + isolationBuilder => isolationBuilder.UseMultiTenant()); var serviceProvider = _services.BuildServiceProvider(); var customDbContext = serviceProvider.GetRequiredService(); var unitOfWorkAccessor = serviceProvider.GetRequiredService(); diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs index bcdd1cc22..345f540e1 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Tests/_Imports.cs @@ -5,9 +5,7 @@ global using Masa.BuildingBlocks.Isolation.MultiTenant; global using Masa.BuildingBlocks.Isolation.Options; global using Masa.Contrib.Isolation.Environment; -global using Masa.Contrib.Isolation.MultiTenancy; global using Masa.Contrib.Isolation.UoW.EF.Parser.Environment; -global using Masa.Contrib.Isolation.UoW.EF.Parser.MultiTenancy; global using Masa.Utils.Data.EntityFrameworkCore; global using Masa.Utils.Data.EntityFrameworkCore.Sqlite; global using Microsoft.AspNetCore.Http; diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs index 1b09bab36..921337ae9 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EdgeDriverTest.cs @@ -1,43 +1,42 @@ -namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests +namespace Masa.Contrib.Isolation.UoW.EF.Web.Tests; + +[TestClass] +public class EdgeDriverTest { - [TestClass] - public class EdgeDriverTest - { - private IServiceCollection _services; + private IServiceCollection _services; - [TestInitialize] - public void Initialize() - { - var configurationRoot = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", true, true) - .Build(); - _services = new ServiceCollection(); - _services.AddSingleton(configurationRoot); - _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW(dbOptions => dbOptions.UseSqlite(), - isolationBuilder => isolationBuilder.SetTenantKey("tenant").SetEnvironmentKey("env").UseMultiTenancy().UseEnvironment())); - System.Environment.SetEnvironmentVariable("env","pro"); - } + [TestInitialize] + public void Initialize() + { + var configurationRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .Build(); + _services = new ServiceCollection(); + _services.AddSingleton(configurationRoot); + _services.AddEventBus(eventBusBuilder => eventBusBuilder.UseIsolationUoW(dbOptions => dbOptions.UseSqlite(), + isolationBuilder => isolationBuilder.SetTenantKey("tenant").SetEnvironmentKey("env").UseMultiTenant().UseEnvironment())); + System.Environment.SetEnvironmentVariable("env", "pro"); + } - [TestMethod] - public async Task TestTenancyAsync() - { - var serviceProvider = _services.BuildServiceProvider(); + [TestMethod] + public async Task TestTenantAsync() + { + var serviceProvider = _services.BuildServiceProvider(); - #region Manually assign values to tenants and environments, and in real scenarios, automatically parse and assign values based on the current HttpContext + #region Manually assign values to tenants and environments, and in real scenarios, automatically parse and assign values based on the current HttpContext - var httpContextAccessor = serviceProvider.GetRequiredService(); - httpContextAccessor.HttpContext = new DefaultHttpContext(); - httpContextAccessor.HttpContext.Items = new Dictionary() - { - { "tenant", "2" } - }; + var httpContextAccessor = serviceProvider.GetRequiredService(); + httpContextAccessor.HttpContext = new DefaultHttpContext(); + httpContextAccessor.HttpContext.Items = new Dictionary() + { + { "tenant", "2" } + }; - #endregion + #endregion - var registerUserEvent = new RegisterUserEvent("jim", "123456"); - var eventBus = serviceProvider.GetRequiredService(); - await eventBus.PublishAsync(registerUserEvent); - } + var registerUserEvent = new RegisterUserEvent("jim", "123456"); + var eventBus = serviceProvider.GetRequiredService(); + await eventBus.PublishAsync(registerUserEvent); } } diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs index 7bb991e8f..f788d9939 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/EventHandlers/RegisterUserEventHandler.cs @@ -42,7 +42,7 @@ public async Task RegisterUserAsync(RegisterUserEvent @event) var user3 = await _customDbContext.Set().FirstOrDefaultAsync(); Assert.IsNull(user3); - using (_dataFilter.Disable()) + using (_dataFilter.Disable()) { var user4 = await _customDbContext.Set().FirstOrDefaultAsync(); Assert.IsNotNull(user4); diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj index f5f1cfdd3..8293b0dce 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/Masa.Contrib.Isolation.UoW.EF.Web.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs index 760bdb8b1..df58f49a4 100644 --- a/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs +++ b/test/Masa.Contrib.Isolation.UoW.EF.Web.Tests/_Imports.cs @@ -2,7 +2,7 @@ global using Masa.BuildingBlocks.Isolation; global using Masa.Contrib.Dispatcher.Events; global using Masa.Contrib.Isolation.Environment; -global using Masa.Contrib.Isolation.MultiTenancy; +global using Masa.Contrib.Isolation.MultiTenant; global using Masa.Contrib.Isolation.UoW.EF.Web.Tests.Events; global using Masa.Utils.Data.EntityFrameworkCore; global using Masa.Utils.Data.EntityFrameworkCore.Filters; @@ -18,4 +18,3 @@ global using System; global using System.IO; global using System.Threading.Tasks; - From d7a3842557b5907477569456afc7400f02d274ff Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 19:11:25 +0800 Subject: [PATCH 10/14] docs(Isolation): change IEnvironment to IMultiEnvironment --- src/Isolation/Masa.Contrib.Isolation.Environment/README.md | 4 ++-- .../Masa.Contrib.Isolation.Environment/README.zh-CN.md | 4 ++-- src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md | 4 ++-- src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md index 54ff35309..ed30c3121 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md @@ -53,9 +53,9 @@ public class CustomDbContext : IsolationDbContext } ```` -4. The class corresponding to the isolated table needs to implement IEnvironment +4. The class corresponding to the isolated table needs to implement IMultiEnvironment -You can also choose not to implement IEnvironment when using physical isolation +You can also choose not to implement IMultiEnvironment when using physical isolation ##### Summarize diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md index 1197c3934..ee6305f0f 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md @@ -54,9 +54,9 @@ public class CustomDbContext : IsolationDbContext } ``` -4. 隔离的表对应的类需要实现IEnvironment +4. 隔离的表对应的类需要实现IMultiEnvironment -采用物理隔离时也可以选择不实现IEnvironment +采用物理隔离时也可以选择不实现IMultiEnvironment ##### 总结 diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index 2b1c500ff..311cf4019 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -61,9 +61,9 @@ public class CustomDbContext : IsolationDbContext } ``` -4. The class corresponding to the isolated table needs to implement IMultiTenant or IEnvironment +4. The class corresponding to the isolated table needs to implement IMultiTenant or IMultiEnvironment -Tenant isolation implements IMultiTenant, and environment isolation implements IEnvironment +Tenant isolation implements IMultiTenant, and environment isolation implements IMultiEnvironment ##### Summarize * When are tenants and environments resolved? diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index e477a6d2f..12601286e 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -61,9 +61,9 @@ public class CustomDbContext : IsolationDbContext } ``` -4. 隔离的表对应的类需要实现IMultiTenant或IEnvironment +4. 隔离的表对应的类需要实现IMultiTenant或IMultiEnvironment -租户隔离实现IMultiTenant、环境隔离实现IEnvironment +租户隔离实现IMultiTenant、环境隔离实现IMultiEnvironment ##### 总结 * 租户与环境什么时候被解析? From 345a889acd736bb2288056ab47943dcfef3d2b21 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 22:03:14 +0800 Subject: [PATCH 11/14] docs(Isolation): change Isolation doc --- .../Masa.Contrib.Isolation.Environment/README.md | 4 ++-- .../README.zh-CN.md | 4 ++-- .../Masa.Contrib.Isolation.MultiTenant/README.md | 14 +++++++++++--- .../README.zh-CN.md | 14 +++++++++++--- .../Masa.Contrib.Isolation.UoW.EF/README.md | 10 +++++++--- .../Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md | 10 +++++++--- 6 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md index ed30c3121..85bb35334 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.md @@ -60,7 +60,7 @@ You can also choose not to implement IMultiEnvironment when using physical isola ##### Summarize * How is the environment resolved in the controller or MinimalAPI? - * The environment provides 1 parser by default, and the execution order is: EnvironmentVariablesParserProvider (obtained in the system environment variable, the default environment parameter: ASPNETCORE_ENVIRONMENT) + * The environment provides one parser by default, and the execution order is: EnvironmentVariablesParserProvider (gets the parameters in the system environment variables, the parameters of the default environment isolation: ASPNETCORE_ENVIRONMENT) * If the parser fails to resolve the environment, what is the last database used? * If the parsing environment fails, return DefaultConnection directly * How to change the default environment parameter name @@ -82,7 +82,7 @@ builder.Services.AddEventBus(eventBusBuilder => dbOptions => dbOptions.UseSqlServer(), isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() { - new EnvironmentVariablesParserProvider() + new EnvironmentVariablesParserProvider() //By default, environment information in environment isolation is obtained from system environment variables }).UseEnvironment());// Use environment isolation }); ```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md index ee6305f0f..72f0ffa27 100644 --- a/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.Environment/README.zh-CN.md @@ -61,7 +61,7 @@ public class CustomDbContext : IsolationDbContext ##### 总结 * 控制器或MinimalAPI中环境如何解析? - * 环境默认提供了1个解析器,执行顺序为:EnvironmentVariablesParserProvider (在系统环境变量中获取,环境参数默认:ASPNETCORE_ENVIRONMENT) + * 环境默认提供了1个解析器,执行顺序为:EnvironmentVariablesParserProvider (获取系统环境变量中的参数,默认环境隔离的参数:ASPNETCORE_ENVIRONMENT) * 如果解析器解析环境失败,那最后使用的数据库是? * 若解析环境失败,则直接返回DefaultConnection * 如何更改默认环境参数名 @@ -83,7 +83,7 @@ builder.Services.AddEventBus(eventBusBuilder => dbOptions => dbOptions.UseSqlServer(), isolationBuilder => isolationBuilder.SetEnvironmentParsers(new List() { - new EnvironmentVariablesParserProvider() + new EnvironmentVariablesParserProvider() // 默认从系统环境变量中获取环境隔离中的环境信息 }).UseEnvironment());// 使用环境隔离 }); ``` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index 4b6b85f52..bea4aad0e 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -64,7 +64,14 @@ You can also choose not to implement IMultiTenant when using physical isolation ##### Summarize * How to resolve the tenant in the controller or MinimalAPI? - * The tenant provides 6 parsers by default, HttpContextItemTenantParserProvider、the execution order is: QueryStringTenantParserProvider, FormTenantParserProvider, RouteTenantParserProvider, HeaderTenantParserProvider, CookieTenantParserProvider (tenant parameter default: __tenant) + * The tenant provides 6 parsers by default, the execution order is: HttpContextItemTenantParserProvider, QueryStringTenantParserProvider, FormTenantParserProvider, RouteTenantParserProvider, HeaderTenantParserProvider, CookieTenantParserProvider (tenant parameter default: __tenant) + * HttpContextItemTenantParserProvider: Obtain tenant information through the Items property of the requested HttpContext + * QueryStringTenantParserProvider: Get tenant information through the requested QueryString + * https://github.com/masastack?__tenant=1 (tenant id is 1) + * FormTenantParserProvider: Get tenant information through the Form form + * RouteTenantParserProvider: Get tenant information through routing + * HeaderTenantParserProvider: Get tenant information through request headers + * CookieTenantParserProvider: Get tenant information through cookies * If the resolver fails to resolve the tenant, what is the last database used? * If the resolution of the tenant fails, return the DefaultConnection directly * How to change the default tenant parameter name @@ -77,7 +84,7 @@ builder.Services.AddEventBus(eventBusBuilder => isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// Use tenant isolation }); ```` -* How to change the parser +* The default parser is not easy to use, want to change the default parser? ```` C# builder.Services.AddEventBus(eventBusBuilder => @@ -86,7 +93,8 @@ builder.Services.AddEventBus(eventBusBuilder => dbOptions => dbOptions.UseSqlServer(), isolationBuilder => isolationBuilder.SetTenantParsers(new List() { - new QueryStringTenantParserProvider() + new QueryStringTenantParserProvider() // only use QueryStringTenantParserProvider, other parsers are removed }).UseMultiTenant());// Use tenant isolation }); +```` ```` \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index 1d4ef1107..43f8db956 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -65,6 +65,13 @@ public class CustomDbContext : IsolationDbContext * 控制器或MinimalAPI中租户如何解析? * 租户默认提供了6个解析器,执行顺序分别为:HttpContextItemTenantParserProvider、QueryStringTenantParserProvider、FormTenantParserProvider、RouteTenantParserProvider、HeaderTenantParserProvider、CookieTenantParserProvider (租户参数默认:__tenant) + * HttpContextItemTenantParserProvider: 通过请求的HttpContext的Items属性获取租户信息 + * QueryStringTenantParserProvider: 通过请求的QueryString获取租户信息 + * https://github.com/masastack?__tenant=1 (租户id为1) + * FormTenantParserProvider: 通过Form表单获取租户信息 + * RouteTenantParserProvider: 通过路由获取租户信息 + * HeaderTenantParserProvider: 通过请求头获取租户信息 + * CookieTenantParserProvider: 通过Cookie获取租户信息 * 如果解析器解析租户失败,那最后使用的数据库是? * 若解析租户失败,则直接返回DefaultConnection * 如何更改默认租户参数名 @@ -77,7 +84,7 @@ builder.Services.AddEventBus(eventBusBuilder => isolationBuilder => isolationBuilder.SetTenantKey("tenant").UseMultiTenant());// 使用租户隔离 }); ``` -* 如何更改解析器 +* 默认解析器不好用,希望更改默认解析器? ``` C# builder.Services.AddEventBus(eventBusBuilder => @@ -86,7 +93,8 @@ builder.Services.AddEventBus(eventBusBuilder => dbOptions => dbOptions.UseSqlServer(), isolationBuilder => isolationBuilder.SetTenantParsers(new List() { - new QueryStringTenantParserProvider() + new QueryStringTenantParserProvider() // 只使用QueryStringTenantParserProvider, 其它解析器移除掉 }).UseMultiTenant());// 使用租户隔离 }); -``` \ No newline at end of file +``` + diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md index 311cf4019..37af358e0 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.md @@ -66,6 +66,10 @@ public class CustomDbContext : IsolationDbContext Tenant isolation implements IMultiTenant, and environment isolation implements IMultiEnvironment ##### Summarize -* When are tenants and environments resolved? - * Manipulating DbContext, Repository, etc. in EventHandler can be used directly without any additional operations and no increase in workload. When Event is published, the environment and tenant information will be parsed through the provided parser. If the environment or tenant has been assigned, then will skip parsing - * To operate DbContext, Repository, etc. directly in the controller or MinimalAPI, you need to add `app.UseIsolation();` in Program.cs, the environment and tenant information will be parsed in the middleware of AspNetCore, if the environment or tenant has been assigned, will skip parsing \ No newline at end of file +* How many kinds of parser are currently supported? + * Currently two kinds of parsers are supported, one is [Environment Parser](../Masa.Contrib.Isolation.Environment/README.zh-CN.md), the other is [Tenant Parser](../Masa.Contrib .Isolation.MultiTenant/README.zh-CN.md) +* How is the parser used? + * After publishing events through EventBus, EventBus will automatically call the parser middleware to trigger the environment and tenant parser to parse and assign values according to the isolation usage + * For scenarios where EventBus is not used, `app.UseIsolation();` needs to be added to Program.cs. After the request is initiated, it will first pass through the AspNetCore middleware provided by Isolation, and trigger the environment and tenant resolvers to parse and assign values. When the request arrives at the specified controller or Minimal method, the parsing is complete +* What does the parser do? + * Obtain the current environment and tenant information through the parser to provide data support for isolation, which needs to be called and executed before creating DbContext \ No newline at end of file diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md index 12601286e..8875d05cc 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/README.zh-CN.md @@ -66,6 +66,10 @@ public class CustomDbContext : IsolationDbContext 租户隔离实现IMultiTenant、环境隔离实现IMultiEnvironment ##### 总结 -* 租户与环境什么时候被解析? - * 在Event被Publish会通过提供的解析器解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 - * 不使用EventBus,直接在控制器或MinimalAPI中操作DbContext、Repository等需要在Program.cs中添加`app.UseIsolation();`,在AspNetCore的中间件中会解析环境、租户信息,如果环境或者租户已经被赋值,则会跳过解析 +* 解析器目前支持几种? + * 目前支持两种解析器,一个是[环境解析器](../Masa.Contrib.Isolation.Environment/README.zh-CN.md)、一个是[租户解析器](../Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md) +* 解析器如何使用? + * 通过EventBus发布事件后,EventBus会自动调用解析器中间件,根据隔离性使用情况触发环境、租户解析器进行解析并赋值 + * 针对未使用EventBus的场景,需要在Program.cs中添加`app.UseIsolation();`,在请求发起后会先经过Isolation提供的AspNetCore中间件,并触发环境、租户解析器进行解析并赋值,当请求到达指定的控制器或者Minimal的方法时已经解析完成 +* 解析器的作用? + * 通过解析器获取当前的环境以及租户信息,为隔离提供数据支撑,需要在创建DbContext之前被调用执行 \ No newline at end of file From b3d3aa8222746957b9d9107f507ad4b929623289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=AC=E9=9B=A8=E5=A3=B0?= Date: Thu, 31 Mar 2022 22:04:46 +0800 Subject: [PATCH 12/14] Update README.md --- src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index bea4aad0e..7d11e64ff 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -97,4 +97,3 @@ builder.Services.AddEventBus(eventBusBuilder => }).UseMultiTenant());// Use tenant isolation }); ```` -```` \ No newline at end of file From c2a5ea70dd781708b385a1a4cdad6daa424cb94d Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Thu, 31 Mar 2022 22:06:01 +0800 Subject: [PATCH 13/14] docs(Isolation): delete erroneous documents --- src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md | 2 +- .../Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md index 7d11e64ff..9e1092506 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.md @@ -6,7 +6,7 @@ Example: ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.MultiTenant // Multi-tenant isolation On-demand reference +Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` diff --git a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md index 43f8db956..44f0c5971 100644 --- a/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md +++ b/src/Isolation/Masa.Contrib.Isolation.MultiTenant/README.zh-CN.md @@ -6,7 +6,7 @@ ```C# Install-Package Masa.Contrib.Isolation.UoW.EF -Install-Package Masa.Contrib.Isolation.MultiTenant // 多租户隔离 按需引用 +Install-Package Masa.Contrib.Isolation.MultiTenant Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` From 7ff7d4738d3f8608e9c3d69f00f2f8b08f720f31 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Fri, 1 Apr 2022 10:16:24 +0800 Subject: [PATCH 14/14] refactor(Isolation): Adjust message implementation --- .../DefaultConnectionStringProvider.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs index d7fae1d3a..d130c3177 100644 --- a/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs +++ b/src/Isolation/Masa.Contrib.Isolation.UoW.EF/DefaultConnectionStringProvider.cs @@ -77,12 +77,11 @@ private string SetConnectionString(string? connectionString = null) private string GetMessage() { - StringBuilder stringBuilder = new StringBuilder(); + List messages = new List(); if (_environmentContext != null) - stringBuilder.Append($"Environment: [{_environmentContext.CurrentEnvironment}], "); + messages.Add($"Environment: [{_environmentContext.CurrentEnvironment ?? ""}]"); if (_tenantContext != null) - stringBuilder.Append($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}], "); - var message = stringBuilder.ToString(); - return message.Substring(0, message.Length - 2); + messages.Add($"Tenant: [{_tenantContext.CurrentTenant?.Id ?? ""}]"); + return string.Join(", ", messages); } }