Abp框架多租户源码解读及自定义拓展

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

posted @ 2019-08-08 16:15  江流天地外  阅读(1071)  评论(0编辑  收藏  举报