企業級工作流解決方案(十三)--集成Abp和ng-alain--數據庫讀寫分離


  說到程序里面數據庫管理,無非就是兩件事情,一是數據庫操作,對於數據庫的操作,各種程序語言都有封裝,也就是所謂的ORM框架,.net 方向一般用得比較多和就是.net framework和dapper,abp里還集成了NHibernate,另外就是連接字符串的管理,簡單的應用直接用一個數據庫連接字符串就可以了,但是對於大型的應用,比如有多租戶概念的系統,比如有一些分庫分表需求的設計系統,那么連接字符串的管理將是非常復雜和核心的內容。

  對於讀寫分離,大家應該比較熟悉,數據庫層面,大型的關系型數據庫都支持,這里的讀寫分離是指代碼層面,針對DBA已經做好的數據庫讀寫分離來管理數據庫連接字符串。

  Abp基本框架提供了最基礎的數據庫連接字符串管理,zero項目實現了多租戶的數據庫連接管理,即把每個租戶的連接字符串存儲在租戶里面,對於每一個Uow操作,都會找租戶的連接字符串,如果找到,就使用,沒有找到,向上層找默認的連接字符串。代碼如下:

/// <summary>
    /// Implements <see cref="IDbPerTenantConnectionStringResolver"/> to dynamically resolve
    /// connection string for a multi tenant application.
    /// </summary>
    public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
    {
        /// <summary>
        /// Reference to the session.
        /// </summary>
        public IAbpSession AbpSession { get; set; }

        private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
        private readonly ITenantCache _tenantCache;

        /// <summary>
        /// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
        /// </summary>
        public DbPerTenantConnectionStringResolver(
            IAbpStartupConfiguration configuration,
            ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
            ITenantCache tenantCache)
            : base(configuration)
        {
            _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
            _tenantCache = tenantCache;

            AbpSession = NullAbpSession.Instance;
        }

        public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {
            if (args.MultiTenancySide == MultiTenancySides.Host)
            {
                return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
            }

            return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
        }

        public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
        {
            if (args.TenantId == null)
            {
                //Requested for host
                return base.GetNameOrConnectionString(args);
            }

            var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
            if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
            {
                //Tenant has not dedicated database
                return base.GetNameOrConnectionString(args);
            }

            return tenantCacheItem.ConnectionString;
        }

        protected virtual int? GetCurrentTenantId()
        {
            return _currentUnitOfWorkProvider.Current != null
                ? _currentUnitOfWorkProvider.Current.GetTenantId()
                : AbpSession.TenantId;
        }
    }

  那么我們要改造的地方其他就可以參照這個來管理連接字符串

  還有一個問題,就是我們怎么讓框架知道我們使用的是讀庫還是寫庫呢?

  Abp里面,公開給用戶控制Uow的,就是UnitOfWorkAttribute裝飾器,增加一個讀庫還是寫庫的標識IsReadDb,在UnitOfWorkOptions類里面也要加對應的屬性,那么我們在構造UnitOfWorkOptions類的時候,可以把屬性裝飾器里面的IsReadDb屬性賦值給UnitOfWorkOptions,再獲取DbContext方法的時候,把此參數傳入Uow連接字符串管理,在連接字符串管理里面,判斷此參數的值,確定數據庫字符串選擇。

  主要代碼:

/// <summary>
    /// Unit of work options.
    /// </summary>
    public class UnitOfWorkOptions
    {
        // ......

        /// <summary>
        /// 自定義:設置是否是讀庫
        /// </summary>
        public bool IsReadDb { get; set; }
}

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
    public class UnitOfWorkAttribute : Attribute
    {
        // ......

        /// <summary>
        /// 自定義:設置是否是讀庫
        /// </summary>
        public bool IsReadDb { get; set; }
        public UnitOfWorkOptions CreateOptions()
        {
            return new UnitOfWorkOptions
            {
                IsTransactional = IsTransactional,
                IsolationLevel = IsolationLevel,
                Timeout = Timeout,
                Scope = Scope,
                IsReadDb = IsReadDb
            };
        }
    }

  在獲取DbContext方法的時候,傳遞數據庫讀寫參數

public static class UnitOfWorkExtensions
    {
        public static TDbContext GetDbContext<TDbContext>(this IActiveUnitOfWork unitOfWork, MultiTenancySides? multiTenancySide = null, string name = null)
            where TDbContext : DbContext
        {
            if (unitOfWork == null)
            {
                throw new ArgumentNullException("unitOfWork");
            }

            if (!(unitOfWork is EfCoreUnitOfWork))
            {
                throw new ArgumentException("unitOfWork is not type of " + typeof(EfCoreUnitOfWork).FullName, "unitOfWork");
            }

            return (unitOfWork as EfCoreUnitOfWork).GetOrCreateDbContext<TDbContext>(multiTenancySide, name, unitOfWork.Options.IsReadDb);
        }
}

public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null, string name = null,bool isReadDb = false)
            where TDbContext : DbContext
        {
            var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext));

            var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
            connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
            connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
            connectionStringResolveArgs["IsReadDb"] = isReadDb;
            var connectionString = ResolveConnectionString(connectionStringResolveArgs);
}

  最終,參照Abp連接字符串的管理,代碼如下:

public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
    {
        /// <summary>
        /// Reference to the session.
        /// </summary>
        public IAbpSession AbpSession { get; set; }

        private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;

        /// <summary>
        /// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
        /// </summary>
        public DbPerTenantConnectionStringResolver(
            IAbpStartupConfiguration configuration,
            ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
            : base(
                  configuration)
        {
            _currentUnitOfWorkProvider = currentUnitOfWorkProvider;
            AbpSession = NullAbpSession.Instance;
        }

        public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
        {
            if (args.MultiTenancySide == MultiTenancySides.Host)
            {
                return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
            }

            return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
        }

        public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
        {
            if (args.TenantId == null)
            {
                //Requested for host
                return base.GetNameOrConnectionString(args);
            }

            var tenantCacheItem = Rpc.Call<Sys_TenantQueryDto>("CommonService.CommonServiceServiceAppService.GetTenantInfo", args.TenantId);
            if(tenantCacheItem == null)
            {
                return base.GetNameOrConnectionString(args);
            }

            if(Convert.ToBoolean(args["IsReadDb"]))
            {
                if(!string.IsNullOrEmpty(tenantCacheItem.ReadConnectionString))
                {
                    return tenantCacheItem.ReadConnectionString;
                }
                else
                {
                    return base.GetNameOrConnectionString(args);
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(tenantCacheItem.ConnectionString))
                {
                    return tenantCacheItem.ConnectionString;
                }
                else
                {
                    return base.GetNameOrConnectionString(args);
                }
            }
        }

        protected virtual int? GetCurrentTenantId()
        {
            return _currentUnitOfWorkProvider.Current != null
                ? _currentUnitOfWorkProvider.Current.GetTenantId()
                : AbpSession.TenantId;
        }
    }

  使用的時候,在類或者方法上,增加Uow屬性裝飾器上定義參數即可

  補充說明:可以參照這種方式,自定義的擴展,比如每一個DbContext自定義連接字符串,我們可以在自己的租戶管理表中添加屬性,自定義數據庫連接字符串選擇邏輯。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM