diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs index 4aeb7766..e8044cdf 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs @@ -66,11 +66,20 @@ namespace Yi.Framework.SqlSugarCore.Abstractions public static string MasterTenantDbDefaultName = "Master"; public static string TenantDbDefaultName = "Default"; + /// + /// 获取默认数据库 + /// + /// public SaasMultiTenancyOptions GetDefaultSaasMultiTenancy() { return new SaasMultiTenancyOptions { Name = TenantDbDefaultName, Url = Url }; } - public SaasMultiTenancyOptions? GetDefaultMasterSaasMultiTenancy() + + /// + /// 获取主数据库 + /// + /// + public SaasMultiTenancyOptions? GetMasterSaasMultiTenancy() { if (EnabledSaasMultiTenancy == false) { diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs index 54637f83..1dd4a823 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs @@ -24,6 +24,9 @@ namespace Yi.Framework.SqlSugarCore currentDb.Aop.DataExecuted = this.DataExecuted; OnSqlSugarClientConfig(currentDb); } + + + public ConnectionConfig Build(Action? action=null) { var dbConnOptions = Options; diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs index 7efaa086..6b57fcd6 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -24,7 +24,7 @@ namespace Yi.Framework.SqlSugarCore /// public ISqlSugarClient SqlSugarClient { get; private set; } public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); - + private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantDbDefaultName; private IAbpLazyServiceProvider LazyServiceProvider { get; } private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService(); @@ -37,7 +37,7 @@ namespace Yi.Framework.SqlSugarCore public IEntityChangeEventHelper EntityChangeEventHelper => LazyServiceProvider.LazyGetService(NullEntityChangeEventHelper.Instance); public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; - + private ISqlSugarDbConnectionCreator _dbConnectionCreator; public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient) { @@ -47,16 +47,77 @@ namespace Yi.Framework.SqlSugarCore { LazyServiceProvider = lazyServiceProvider; var connectionCreator = LazyServiceProvider.LazyGetRequiredService(); + _dbConnectionCreator = connectionCreator; connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig; connectionCreator.EntityService = EntityService; connectionCreator.DataExecuting = DataExecuting; connectionCreator.DataExecuted = DataExecuted; connectionCreator.OnLogExecuting = OnLogExecuting; connectionCreator.OnLogExecuted = OnLogExecuted; - SqlSugarClient = new SqlSugarClient(connectionCreator.Build()); - connectionCreator.SetDbAop(SqlSugarClient); + SqlSugarClient = new SqlSugarClient(connectionCreator.Build()); + var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService(); + var connectionStr = connectionStringResolver.ResolveAsync().Result; + var changedDb = DatabaseChange(this, connectionStr); + SqlSugarClient = changedDb.SqlSugarClient; } + /// + /// db切换多库支持 + /// + /// + /// + /// + protected virtual SqlSugarDbContext DatabaseChange(SqlSugarDbContext dbContext, string connectionString) + { + string configId = string.Empty; + //没有检测到使用多租户功能,默认使用默认库即可 + if (string.IsNullOrWhiteSpace(connectionString)) + { + connectionString = dbContext.Options.Url; + configId = CurrentTenant.Name; + } + + var dbOption = dbContext.Options; + var db = dbContext.SqlSugarClient.AsTenant(); + + //主库的Db切换,当操作的是租户表的时候 + if (CurrentTenant.Name == MasterTenantDbDefaultName) + { + //直接切换 + configId = MasterTenantDbDefaultName; + var conStrOrNull = dbOption.GetMasterSaasMultiTenancy(); + Volo.Abp.Check.NotNull(conStrOrNull, "租户主库未找到"); + connectionString = conStrOrNull.Url; + } + + //租户Db的动态切换 + //二级缓存 + var changed = false; + if (!db.IsAnyConnection(configId)) + { + var config = _dbConnectionCreator.Build(options => + { + options.DbType = dbOption.DbType!.Value; + options.ConfigId = configId;//设置库的唯一标识 + options.IsAutoCloseConnection = true; + options.ConnectionString = connectionString; + }); + //添加一个db到当前上下文 (Add部分不线上下文不会共享) + db.AddConnection(config); + changed = true; + } + var currentDb = db.GetConnection(configId) as ISqlSugarClient; + //设置Aop + if (changed) + { + _dbConnectionCreator.SetDbAop(currentDb); + } + dbContext.SetSqlSugarClient(currentDb); + + return dbContext; + } + + /// /// 上下文对象扩展 /// diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs index 44ea1419..6e49fa9f 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs @@ -20,7 +20,7 @@ namespace Yi.Framework.SqlSugarCore.Uow public async Task CommitAsync(CancellationToken cancellationToken = default) { - await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync(); + // await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync(); } public void Dispose() @@ -29,7 +29,7 @@ namespace Yi.Framework.SqlSugarCore.Uow public async Task RollbackAsync(CancellationToken cancellationToken = default) { - await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync(); + // await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync(); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs index c04531a1..4c5317e9 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs @@ -14,7 +14,7 @@ namespace Yi.Framework.SqlSugarCore.Uow public class UnitOfWorkSqlsugarDbContextProvider : ISugarDbContextProvider where TDbContext : ISqlSugarDbContext { private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator; - private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantDbDefaultName; + public ILogger> Logger { get; set; } public IServiceProvider ServiceProvider { get; set; } @@ -59,11 +59,10 @@ namespace Yi.Framework.SqlSugarCore.Uow ContextInstance.Current = (TDbContext)ServiceProvider.GetRequiredService(); } var dbContext = (TDbContext)ContextInstance.Current; - var output = DatabaseChange(dbContext, connectionStringName, connectionString); //提高体验,取消工作单元强制性 //throw new AbpException("A DbContext can only be created inside a unit of work!"); //如果不启用工作单元,创建一个新的db,不开启事务即可 - return output; + return dbContext; } @@ -100,64 +99,10 @@ namespace Yi.Framework.SqlSugarCore.Uow using (SqlSugarDbContextCreationContext.Use(creationContext)) { var dbContext = await CreateDbContextAsync(unitOfWork); - - //获取到DB之后,对多租户多库进行处理 - var changedDbContext = DatabaseChange(dbContext, connectionStringName, connectionString); - return changedDbContext; + return dbContext; } } - protected virtual TDbContext DatabaseChange(TDbContext dbContext, string configId, string connectionString) - { - //没有检测到使用多租户功能,默认使用默认库即可 - if (string.IsNullOrWhiteSpace(connectionString)) - { - connectionString = dbContext.Options.Url; - configId = DbConnOptions.TenantDbDefaultName; - } - - var dbOption = dbContext.Options; - var db = dbContext.SqlSugarClient.AsTenant(); - //主库的Db切换,当操作的是租户表的时候 - if (CurrentTenant.Name == MasterTenantDbDefaultName) - { - //直接切换 - configId = MasterTenantDbDefaultName; - var conStrOrNull = dbOption.GetDefaultMasterSaasMultiTenancy(); - Volo.Abp.Check.NotNull(conStrOrNull, "租户主库未找到"); - connectionString = conStrOrNull.Url; - } - - //租户Db的动态切换 - //二级缓存 - var changed = false; - if (!db.IsAnyConnection(configId)) - { - var config = _dbConnectionCreator.Build(options => - { - options.DbType = dbOption.DbType!.Value; - options.ConfigId = configId;//设置库的唯一标识 - options.IsAutoCloseConnection = true; - options.ConnectionString = connectionString; - }); - //添加一个db到当前上下文 (Add部分不线上下文不会共享) - db.AddConnection(config); - changed = true; - } - var currentDb = db.GetConnection(configId) as ISqlSugarClient; - - //设置Aop - if (changed) - { - _dbConnectionCreator.SetDbAop(currentDb); - } - - - dbContext.SetSqlSugarClient(currentDb); - return dbContext; - } - - protected virtual async Task CreateDbContextAsync(IUnitOfWork unitOfWork) { return unitOfWork.Options.IsTransactional @@ -182,7 +127,7 @@ namespace Yi.Framework.SqlSugarCore.Uow ); unitOfWork.AddTransactionApi(transactionApiKey, transaction); - await dbContext.SqlSugarClient.Ado.BeginTranAsync(); + // await dbContext.SqlSugarClient.Ado.BeginTranAsync(); return dbContext; } else diff --git a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Application/TenantService.cs b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Application/TenantService.cs index f215cdad..87739eac 100644 --- a/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Application/TenantService.cs +++ b/Yi.Abp.Net8/module/tenant-management/Yi.Framework.TenantManagement.Application/TenantService.cs @@ -110,17 +110,18 @@ namespace Yi.Framework.TenantManagement.Application [HttpPut("tenant/init/{id}")] public async Task InitAsync([FromRoute]Guid id) { - using (CurrentTenant.Change(id)) + using (CurrentTenant.Change(id,"test")) { - CodeFirst(await _repository.GetDbContextAsync()); + await CodeFirst(this.LazyServiceProvider); await _dataSeeder.SeedAsync(id); } + } - private void CodeFirst(ISqlSugarClient db) + private async Task CodeFirst(IServiceProvider service) { - - var moduleContainer = ServiceProvider.GetRequiredService(); + var moduleContainer = service.GetRequiredService(); + var db = await _repository.GetDbContextAsync(); //尝试创建数据库 db.DbMaintenance.CreateDatabase(); @@ -131,12 +132,11 @@ namespace Yi.Framework.TenantManagement.Application types.AddRange(module.Assembly.GetTypes() .Where(x => x.GetCustomAttribute() == null) .Where(x => x.GetCustomAttribute() != null) - .Where(x=>x.GetCustomAttribute()==null) .Where(x => x.GetCustomAttribute() is null)); } if (types.Count > 0) { - db.CodeFirst.InitTables(types.ToArray()); + db.CopyNew().CodeFirst.InitTables(types.ToArray()); } } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/DefaultConnectionStringResolver2.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/DefaultConnectionStringResolver2.cs new file mode 100644 index 00000000..a285d577 --- /dev/null +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/DefaultConnectionStringResolver2.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MultiTenancy; + +[Dependency(ReplaceServices = true)] +public class MultiTenantConnectionStringResolver2 : DefaultConnectionStringResolver +{ + private readonly ICurrentTenant _currentTenant; + private readonly IServiceProvider _serviceProvider; + + public MultiTenantConnectionStringResolver2( + IOptionsMonitor options, + ICurrentTenant currentTenant, + IServiceProvider serviceProvider) + : base(options) + { + _currentTenant = currentTenant; + _serviceProvider = serviceProvider; + } + + public override async Task ResolveAsync(string? connectionStringName = null) + { + if (_currentTenant.Id == null) + { + //No current tenant, fallback to default logic + return await base.ResolveAsync(connectionStringName); + } + + var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value); + + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) + { + //Tenant has not defined any connection string, fallback to default logic + return await base.ResolveAsync(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; + + //Requesting default connection string... + //if (connectionStringName == null || + // connectionStringName == ConnectionStrings.DefaultConnectionStringName) + //{ + // //Return tenant's default or global default + // return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + // ? tenantDefaultConnectionString! + // : Options.ConnectionStrings.Default!; + //} + + //Requesting specific connection string... + var connString = tenant.ConnectionStrings?.FirstOrDefault().Value; + if (!connString.IsNullOrWhiteSpace()) + { + //Found for the tenant + return connString!; + } + + //Fallback to the mapped database for the specific connection string + var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName); + if (database != null && database.IsUsedByTenants) + { + connString = tenant.ConnectionStrings?.GetOrDefault(database.DatabaseName); + if (!connString.IsNullOrWhiteSpace()) + { + //Found for the tenant + return connString!; + } + } + + //Fallback to tenant's default connection string if available + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString!; + } + + return await base.ResolveAsync(connectionStringName); + } + + [Obsolete("Use ResolveAsync method.")] + public override string Resolve(string? connectionStringName = null) + { + if (_currentTenant.Id == null) + { + //No current tenant, fallback to default logic + return base.Resolve(connectionStringName); + } + + var tenant = FindTenantConfiguration(_currentTenant.Id.Value); + + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) + { + //Tenant has not defined any connection string, fallback to default logic + return base.Resolve(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; + + //Requesting default connection string... + if (connectionStringName == null || + connectionStringName == ConnectionStrings.DefaultConnectionStringName) + { + //Return tenant's default or global default + return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + ? tenantDefaultConnectionString! + : Options.ConnectionStrings.Default!; + } + + //Requesting specific connection string... + var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName); + if (!connString.IsNullOrWhiteSpace()) + { + //Found for the tenant + return connString!; + } + + //Fallback to tenant's default connection string if available + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString!; + } + + //Try to find the specific connection string for given name + var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connStringInOptions.IsNullOrWhiteSpace()) + { + return connStringInOptions!; + } + + //Fallback to the global default connection string + var defaultConnectionString = Options.ConnectionStrings.Default; + if (!defaultConnectionString.IsNullOrWhiteSpace()) + { + return defaultConnectionString!; + } + + throw new AbpException("No connection string defined!"); + } + + protected virtual async Task FindTenantConfigurationAsync(Guid tenantId) + { + using (var serviceScope = _serviceProvider.CreateScope()) + { + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + return await tenantStore.FindAsync(tenantId); + } + } + + [Obsolete("Use FindTenantConfigurationAsync method.")] + protected virtual TenantConfiguration? FindTenantConfiguration(Guid tenantId) + { + using (var serviceScope = _serviceProvider.CreateScope()) + { + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + return tenantStore.Find(tenantId); + } + } +} diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs index 0feeab19..f8c9f615 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Program.cs @@ -1,5 +1,8 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; using Serilog; using Serilog.Events; +using Volo.Abp.Data; +using Volo.Abp.MultiTenancy; using Yi.Abp.Web; //创建日志,可使用{SourceContext}记录 @@ -23,6 +26,7 @@ try builder.Host.UseAutofac(); builder.Host.UseSerilog(); await builder.Services.AddApplicationAsync(); + builder.Services.Replace(new ServiceDescriptor(typeof(IConnectionStringResolver), typeof(MultiTenantConnectionStringResolver2),ServiceLifetime.Transient)); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db index f46d3d4e..6899f4ca 100644 Binary files a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db and b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-abp-dev.db differ diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test.db b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test.db new file mode 100644 index 00000000..7d73f400 Binary files /dev/null and b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test.db differ diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test2.db b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test2.db new file mode 100644 index 00000000..c88deb02 Binary files /dev/null and b/Yi.Abp.Net8/src/Yi.Abp.Web/yi-test2.db differ