前言

23种设计模式都会了吗?明天讲一下动态代理模式的实战场景。

代理模式给某一个对象提供一个代理对象,并由代理对象管制对原对象的援用。艰深的来讲代理模式就是咱们生存中常见的中介。

举个例子来阐明:如果说我当初想买一辆二手车,尽管我能够本人去找车源,做品质检测等一系列的车辆过户流程,然而这的确太节约我得工夫和精力了。我只是想买一辆车而已为什么我还要额定做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责抉择本人喜爱的车,而后付钱就能够了。

为什么要用代理模式?

中介隔离作用:在某些状况下,一个客户类不想或者不能间接援用一个委托对象,而代理类对象能够在客户类和委托对象之间起到中介的作用,其特色是代理类和委托类实现雷同的接口。


解决问题

这篇文章借用 FreeSql.Cloud 开源我的项目的代码,解说代理模式的理论利用和收益,以及须要留神的中央。

FreeSql 是 c#.NET 功能强大的 ORM 框架,定义了 IFreeSql 接口,次要针对单个 ConnectionString 生产 ORM 操作对象。

跨多数据库的时候,不同的 ConnectionString 须要生成多个 IFreeSql 原始对象,如果是多租户场景每个租户 ConnectionString 都不雷同的状况下,就须要创立 N个 IFreeSql 原始对象。

FreeSql.Cloud 正是为了跨多数据库的问题而产生,它能够解决:

1、FreeSqlCloud 实现多库版 IFreeSql 接口,从应用习惯上放弃与单库版 IFreeSql 统一;

2、运行时,FreeSqlCloud 可动静增加或删除多个 ConnectionString 对应的 IFreeSql;

3、FreeSqlCloud 存储多租户 IFreeSql,最初沉闷工夫 > 10分钟的租户,开释对应 IFreeSql 缩小内存开销;

4、FreeSqlCloud 反对随时 Change 切换到对应的 IFreeSql 进行操作;


代理模式实战(一)Scoped FreeSqlCloud 多库版本

IFreeSql 是一个极为严格、简略,且功能强大的接口,咱们始终在严格控制 API 泛滥增长,泛滥的 API 在后续革新时十分苦楚。

正因为它的简略定义,让咱们有机会应用到代理模式实现新的 IFreeSql 实现类 FreeSqlCloud。

public class FreeSqlCloud : IFreeSql{    IFreeSql CurrentOrm => ...; //请看前面    public IAdo Ado => CurrentOrm.Ado;    public IAop Aop => CurrentOrm.Aop;    public ICodeFirst CodeFirst => CurrentOrm.CodeFirst;    public IDbFirst DbFirst => CurrentOrm.DbFirst;    public GlobalFilter GlobalFilter => CurrentOrm.GlobalFilter;    public void Transaction(Action handler) => CurrentOrm.Transaction(handler);    public void Transaction(IsolationLevel isolationLevel, Action handler) => CurrentOrm.Transaction(isolationLevel, handler);    public ISelect<T1> Select<T1>() where T1 : class => CurrentOrm.Select<T1>();    public ISelect<T1> Select<T1>(object dywhere) where T1 : class => Select<T1>().WhereDynamic(dywhere);    public IDelete<T1> Delete<T1>() where T1 : class => CurrentOrm.Delete<T1>();    public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => Delete<T1>().WhereDynamic(dywhere);    public IUpdate<T1> Update<T1>() where T1 : class => CurrentOrm.Update<T1>();    public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => Update<T1>().WhereDynamic(dywhere);    public IInsert<T1> Insert<T1>() where T1 : class => CurrentOrm.Insert<T1>();    public IInsert<T1> Insert<T1>(T1 source) where T1 : class => Insert<T1>().AppendData(source);    public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => Insert<T1>().AppendData(source);    public IInsert<T1> Insert<T1>(List<T1> source) where T1 : class => Insert<T1>().AppendData(source);    public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => Insert<T1>().AppendData(source);    public IInsertOrUpdate<T1> InsertOrUpdate<T1>() where T1 : class => CurrentOrm.InsertOrUpdate<T1>();}

如上代码,若 CurrentOrm 返回值是原始 IFreeSql 对象,即会代理调用原始数据库 ORM 操作方法。办法不多,性能却弱小,代理模式利用起来就会很轻松。

因为多个 ConnectionString 的起因,咱们须要定义一个字典保留多个原始 IFreeSql 对象:

    readonly Dictionary<string, IFreeSql> _orms = new Dictionary<string, IFreeSql>();

咱们曾经有了 _orms,还缺什么??还缺一个以后 string _dbkey,有了它咱们的 CurrentOrm 办法才晓得怎么获取对应的 IFreeSql:

    string _dbkey;    IFreeSql CurrentOrm => _orms[_dbkey]; //测试不纠结代码平安    //切换数据库    IFreeSql Change(string db)    {        _dbkey = db;        return _orms[_dbkey];    }    //增加 IFreeSql    FreeSqlCloud Add(string db, IFreeSql orm)    {        if (_dbkey == null) _dbkey = db;        _orms.Add(db, orm);        return this;    }

至此咱们基于 Scoped 生命周期的 FreeSqlCloud 就实现了,DI 代码大略如下:

public void ConfigureServices(IServiceCollection services){    var db1 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build();    var db2 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db2.db").Build();    services.AddScoped(provider =>    {        var cloud = new FreeSqlCloud();        cloud.Add("db1", db1);        cloud.Add("db2", db2);        return cloud;    });}

代理模式实战(二)Singleton FreeSqlCloud 多库版本

实战(一)咱们实现了 Scoped 版本,可是其实我的项目中 Singleton 单例才是高性能的保障,特地是多租户场景,每次 new FreeSqlCloud 不止还要循环 Add 那么屡次,实属节约!!!

其实单例并非难事,只须要将 _dbkey 类型批改成 AsyncLocal,这个类型多线程是平安的,无关它的原理请看材料:https://www.cnblogs.com/event...

    AsyncLocal<string> _dbkey;    IFreeSql CurrentOrm => _orms[_dbkey.Value];    IFreeSql Change(string db)    {        _dbkey.Value = db;        return _orms[_dbkey];    }    FreeSqlCloud Add(string db, IFreeSql orm)    {        if (_dbkey.Value == null) _dbkey.Value = db;        _orms.Add(db, orm);        return this;    }

至此咱们就实现了一个多线程平安的代理模式实现,因而咱们只须要在 Ioc 注入之前 Register 好原始 IFreeSql 对象即可:

public void ConfigureServices(IServiceCollection services){    var db1 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build();    var db2 = new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db2.db").Build();    var cloud = new FreeSqlCloud();    cloud.Add("db1", db1);    cloud.Add("db2", db2);    services.AddSingleton(cloud);}

代理模式实现(三)Singleton FreeSqlCloud 多库多租户版本

如上,咱们应用字典存储多个 IFreeSql 原始对象,在数量不多的状况下是可行的。

然而如果咱们做的是多租户零碎,那么数量很可能达到几百,甚至上千个 IFreeSql 对象,并且这些租户不全都是沉闷状态。

因而咱们须要一种开释机制,当租户最初沉闷工夫 > 10分钟,开释 IFreeSql 资源,缩小内存开销;

咱们能够援用 IdleBus 组件解决该问题,IdleBus 闲暇对象治理容器,无效组织对象反复利用,主动创立、销毁,解决【实例】过多且长时间占用的问题。有时候想做一个单例对象重复使用晋升性能,然而定义多了,有的又可能始终闲暇着占用资源。

IdleBus 专门解决:又想反复利用,又想少占资源的场景。

此时咱们只须要批改外部实现局部代码如下:

public class FreeSqlCloud : IFreeSql{    IdleBus<IFreeSql> _orms = new IdleBus<string, IFreeSql>();    AsyncLocal<string> _dbkey;    IFreeSql CurrentOrm => _orms.Get(_dbkey.Value);    IFreeSql Change(string db)    {        _dbkey.Value = db;        return this;    }    FreeSqlCloud Register(string db, Func<IFreeSql> create) //留神 create 类型是 Func    {        if (_dbkey.Value == null) _dbkey.Value = db;        _orms.Register(db, create);        return this;    }    //...}public void ConfigureServices(IServiceCollection services){    var cloud = new FreeSqlCloud();    cloud.Add("db1", () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build());    cloud.Add("db2", () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, "data source=db1.db").Build());    services.AddSingleton(cloud);}

代理模式实现(四)追随切换数据库的仓储对象

1、动态仓储对象

FreeSql.Repository 对象创立时固定了原始 IFreeSql,因而无奈追随 FreeSqlCloud Change 切换数据库。

留神:是同一个对象实例创立之后,无奈追随切换,创立新对象实例不受影响。

因为要在 Repository 创立之前,先调用 fsql.Change 切换好数据库。

2、动静仓储对象

然而。。。依然有一种非凡需要,Repository 在创立之后,依然能追随 fsql.Change 切换数据库。

实战中 Scoped 生命同期可能有多个 Repository 对象,因而切换 cloud 即扭转所有 Repository 对象状态,才算不便。

var repo1 = cloud.GetCloudRepository<User>();var repo2 = cloud.GetCloudRepository<UserGroup>();cloud.Change("db2");Console.WriteLine(repo1.Orm.Ado.ConnectionString); //repo -> db2Console.WriteLine(repo2.Orm.Ado.ConnectionString); //repo -> db2cloud.Change("db1");Console.WriteLine(repo1.Orm.Ado.ConnectionString); //repo -> db1Console.WriteLine(repo2.Orm.Ado.ConnectionString); //repo -> db1

咱们依然应用了代理模式,IBaseRepository 接口定义也足够简略:

提醒:要害看 CurrentRepo 的获取
class RepositoryCloud<TEntity> : IBaseRepository<TEntity> where TEntity : class{    readonly FreeSqlCloud _cloud;    public RepositoryCloud(FreeSqlCloud cloud)    {        _cloud = cloud;    }    public IBaseRepository<TEntity> CurrentRepo => ...; //请看前面    public IUnitOfWork UnitOfWork    {        get => CurrentRepo.UnitOfWork;        set => CurrentRepo.UnitOfWork = value;    }    public IFreeSql Orm => CurrentRepo.Orm;    public Type EntityType => CurrentRepo.EntityType;    public IDataFilter<TEntity> DataFilter => CurrentRepo.DataFilter;    public ISelect<TEntity> Select => CurrentRepo.Select;    public IUpdate<TEntity> UpdateDiy => CurrentRepo.UpdateDiy;    public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => CurrentRepo.Where(exp);    public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => CurrentRepo.WhereIf(condition, exp);    public void Attach(TEntity entity) => CurrentRepo.Attach(entity);    public void Attach(IEnumerable<TEntity> entity) => CurrentRepo.Attach(entity);    public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => CurrentRepo.AttachOnlyPrimary(data);    public Dictionary<string, object[]> CompareState(TEntity newdata) => CurrentRepo.CompareState(newdata);    public void FlushState() => CurrentRepo.FlushState();    public void BeginEdit(List<TEntity> data) => CurrentRepo.BeginEdit(data);    public int EndEdit(List<TEntity> data = null) => CurrentRepo.EndEdit(data);    public TEntity Insert(TEntity entity) => CurrentRepo.Insert(entity);    public List<TEntity> Insert(IEnumerable<TEntity> entitys) => CurrentRepo.Insert(entitys);    public TEntity InsertOrUpdate(TEntity entity) => CurrentRepo.InsertOrUpdate(entity);    public void SaveMany(TEntity entity, string propertyName) => CurrentRepo.SaveMany(entity, propertyName);    public int Update(TEntity entity) => CurrentRepo.Update(entity);    public int Update(IEnumerable<TEntity> entitys) => CurrentRepo.Update(entitys);    public int Delete(TEntity entity) => CurrentRepo.Delete(entity);    public int Delete(IEnumerable<TEntity> entitys) => CurrentRepo.Delete(entitys);    public int Delete(Expression<Func<TEntity, bool>> predicate) => CurrentRepo.Delete(predicate);    public List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate) => CurrentRepo.DeleteCascadeByDatabase(predicate);}

如上代码要害实现局部 CurrentRepo,咱们定义了字典存储多个 IBaseRepository<TEntity> 原始仓储对象:

因为一个 CloudRepository 对象会创立 1-N 个 IBaseRepository 原始对象,在不应用 cloud.Change(..) 办法的时候只会创立 1 个,最多创立 cloud.Registers 数量,实在场景中不会有人在同一个业务把所有 db 都切换个遍。

    readonly Dictionary<string, IBaseRepository<TEntity>> _repos = new Dictionary<string, IBaseRepository<TEntity>>();    protected void ForEachRepos(Action<IBaseRepository<TEntity>> action)    {        foreach (var repo in _repos.Values) action(repo);    }    public void Dispose()    {        ForEachRepos(repo => repo.Dispose());        _repos.Clear();    }        protected IBaseRepository<TEntity> CurrentRepo    {        get        {            var dbkey = _cloud._dbkey.Value;            if (_repos.TryGetValue(dbkey, out var repo) == false)            {                _repos.Add(dbkey, repo = _cloud.Use(dbkey).GetRepository<TEntity>());                if (_dbContextOptions == null) _dbContextOptions = repo.DbContextOptions;                else                {                    repo.DbContextOptions = _dbContextOptions;                    if (_asTypeEntityType != null) repo.AsType(_asTypeEntityType);                    if (_asTableRule != null) repo.AsTable(_asTableRule);                }            }            return repo;        }    }    Type _dbContextOptions;    public DbContextOptions DbContextOptions    {        get => CurrentRepo.DbContextOptions;        set => ForEachRepos(repo => repo.DbContextOptions = value);    }    Type _asTypeEntityType;    public void AsType(Type entityType)    {        _asTypeEntityType = entityType;        ForEachRepos(repo => repo.AsType(entityType));    }    Func<string, string> _asTableRule;    public void AsTable(Func<string, string> rule)    {        _asTableRule = rule;        ForEachRepos(repo => repo.AsTable(rule));    }

因为 DbContextOptions、AsType、AsTable 比拟非凡,须要将多个原始仓储对象流传设置,代码如上。

最终还要为 CloudRepository 创立扩大办法:

public static IBaseRepository<TEntity> GetCloudRepository<TEntity>(this FreeSqlCloud cloud)    where TEntity : class{    return new RepositoryCloud<TEntity>(cloud);}

结语

Repository 是一种十分不便做设计的模式,FreeSql 还有很多一些其余设计模式的利用,如果有趣味当前找机会再写文章。


作者是什么人?

作者是一个入行 18年的老批,他目前写的.net 开源我的项目有:

开源我的项目形容开源地址开源协定
FreeIM聊天零碎架构https://github.com/2881099/Fr...MIT
FreeRedisRedis SDKhttps://github.com/2881099/Fr...MIT
csredishttps://github.com/2881099/cs...MIT
FightLandlord斗DI主网络版https://github.com/2881099/Fi...学习用处
FreeScheduler定时工作https://github.com/2881099/Fr...MIT
IdleBus闲暇容器https://github.com/2881099/Id...MIT
FreeSqlORMhttps://github.com/dotnetcore...MIT
FreeSql.Cloud分布式tcc/sagahttps://github.com/2881099/Fr...MIT
FreeSql.AdminLTE低代码后盾生成https://github.com/2881099/Fr...MIT
FreeSql.DynamicProxy动静代理https://github.com/2881099/Fr...学习用处

须要的请拿走,这些都是最近几年的开源作品,以前更早写的就不发了。