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)
查询当前租户对应的连接字符串。