1、Abp
源碼解析
多租戶連接字符串處理類(EntityFrameworkCore
版本),命名空間為Abp
.Zero
.EntityFrameworkCore
。
/// <summary>
/// Implements <see cref="IDbPerTenantConnectionStringResolver"/> to dynamically resolve
/// connection string for a multi tenant application.
/// </summary>
public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
入口方法:
public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args.MultiTenancySide == MultiTenancySides.Host)
{
return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
}
return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
}
public class ConnectionStringResolveArgs : Dictionary<string, object>
{
public MultiTenancySides? MultiTenancySide { get; set; } // 表示當前是租戶還是Host
public ConnectionStringResolveArgs(MultiTenancySides? multiTenancySide = null)
{
MultiTenancySide = multiTenancySide;
}
}
什么時候調用入口方法?初始化DbContext
的時候。如iRepository.GetAllList()
。
這個方法做了什么事情?
public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
{
if (args.TenantId == null)
{
//Host取默認的連接字符串
return base.GetNameOrConnectionString(args);
}
//從緩存中取值
var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
{
//租戶沒配置連接字符串,則返回默認的連接字符串
return base.GetNameOrConnectionString(args);
}
return tenantCacheItem.ConnectionString;
}
看取Host的連接字符串方法:
public virtual string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
Check.NotNull(args, nameof(args));
var defaultConnectionString = _configuration.DefaultNameOrConnectionString;
if (!string.IsNullOrWhiteSpace(defaultConnectionString))
{
return defaultConnectionString;
}
if (ConfigurationManager.ConnectionStrings["Default"] != null)
{
return "Default";
}
if (ConfigurationManager.ConnectionStrings.Count == 1)
{
return ConfigurationManager.ConnectionStrings[0].ConnectionString;
}
throw new AbpException("Could not find a connection string definition for the application. Set IAbpStartupConfiguration.DefaultNameOrConnectionString or add a 'Default' connection string to application .config file.");
}
問題來了,_tenantCache
中哪來的租戶配置?看這個Get
方法:
public virtual TenantCacheItem Get(int tenantId)
{
var cacheItem = GetOrNull(tenantId);
if (cacheItem == null)
{
throw new AbpException("There is no tenant with given id: " + tenantId);
}
return cacheItem;
}
繼續看GetOrNull
方法:
public TenantCacheItem GetOrNull(int tenantId)
{
return _cacheManager
.GetTenantCache()
.Get(
tenantId,
() =>
{
var tenant = GetTenantOrNull(tenantId);
if (tenant == null)
{
return null;
}
return CreateTenantCacheItem(tenant);
}
);
}
這個其實就是從緩存中取值,並做了補全功能。繼續,看GetTenantOrNull
方法:
[UnitOfWork]
protected virtual TTenant GetTenantOrNull(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
return _tenantRepository.FirstOrDefault(tenantId);
}
}
到這里,其實要去租戶配置表中查詢該租戶配置的信息,所以調用了方法_unitOfWorkManager.Current.SetTenantId(null)
,調用了方法以后,代碼執行到_tenantRepository.FirstOrDefault(tenantId)
時,會重新調用獲取連接字符串的方法,即我們說的入口方法。
最后,拿到Host
的數據庫連接字符串,_tenantRepository.FirstOrDefault(tenantId)
查詢當前租戶對應的連接字符串。