[Abp vNext 源码分析] - 14. EntityFramework Core 的集成
一、简要介绍
在以前的文章里面,我们介绍了 abp vnext 在 ddd 模块定义了仓储的接口定义和基本实现。本章将会介绍,abp vnext 是如何将 entityframework core 框架跟仓储进行深度集成。
abp vnext 在集成 ef core 的时候,不只是简单地实现了仓储模式,除开仓储以外,还提供了一系列的基础设施,如领域事件的发布,数据过滤器的实现。
二、源码分析
entityframeworkcore 相关的模块基本就下面几个,除了第一个是核心 entityframeworkcore 模块以外,其他几个都是封装的 entityframeworkcore provider,方便各种数据库进行集成。
2.1 ef core 模块集成与初始化
首先从 volo.abp.entityframeworkcore 的 abpentityframeworkcoremodule
开始分析,该模块只重写了 configureservices()
方法,在内部也只有两句代码。
public override void configureservices(serviceconfigurationcontext context) { // 调用 abpdbcontextoptions 的预配置方法,为了解决下面的问题。 // https://*.com/questions/55369146/eager-loading-include-with-using-uselazyloadingproxies configure<abpdbcontextoptions>(options => { options.preconfigure(abpdbcontextconfigurationcontext => { abpdbcontextconfigurationcontext.dbcontextoptions .configurewarnings(warnings => { warnings.ignore(coreeventid.lazyloadondisposedcontextwarning); }); }); }); // 注册 idbcontextprovider 组件。 context.services.tryaddtransient(typeof(idbcontextprovider<>), typeof(unitofworkdbcontextprovider<>)); }
首先看第一句代码,它在内部会调用 abpdbcontextoptions
提供的 preconfigure()
方法。这个方法逻辑很简单,会将传入的 action<abpdbcontextconfigurationcontext>
委托添加到一个 list<action<abpdbcontextconfigurationcontext>>
集合,并且在 dbcontextoptionsfactory
工厂中使用。
第二局代码则比较简单,为 idbcontextprovider<>
类型注入默认实现 unitofworkdbcontextprovider<>
。
public class abpdbcontextoptions { internal list<action<abpdbcontextconfigurationcontext>> defaultpreconfigureactions { get; set; } // ... public void preconfigure([notnull] action<abpdbcontextconfigurationcontext> action) { check.notnull(action, nameof(action)); defaultpreconfigureactions.add(action); } // ... }
从上面的代码可以看出来,这个 abpdbcontextconfigurationcontext
就是一个配置上下文,用于 abp vnext 框架在初始化的时候进行各种配置。
2.1.1 ef core provider 的集成
在翻阅 abpdbcontextoptions
代码的时候,我发现除了预配置方法,它还提供了一个 configure([notnull] action<abpdbcontextconfigurationcontext> action)
方法,以及它的泛型重载 configure<tdbcontext>([notnull] action<abpdbcontextconfigurationcontext<tdbcontext>> action)
,它们的内部实现与预配置类似。
这两个方法在 abp vnext 框架内部的应用,主要在各个 ef provider 模块当中有体现。
这里我以 volo.abp.entityframeworkcore.postgresql 模块作为例子,在项目内部只有两个扩展方法的定义类。在 abpdbcontextoptionspostgresqlextensions
当中,就使用到了 configure()
方法。
public static void usepostgresql( [notnull] this abpdbcontextoptions options, [canbenull] action<npgsqldbcontextoptionsbuilder> postgresqloptionsaction = null) { options.configure(context => { // 这里的 context 类型是 abpdbcontextconfigurationcontext。 context.usepostgresql(postgresqloptionsaction); }); }
上面代码中的 usepostgresql()
方法很明显不是 ef core provider 所定义的扩展方法,跳转到具体实现,发现就是一层简单的封装。由于 abpdbcontextconfigurationcontext
内部提供了 dbcontextoptionsbuilder
,所以直接使用这个 dbcontextoptionsbuilder
调用提供的扩展方法即可。
public static class abpdbcontextconfigurationcontextpostgresqlextensions { public static dbcontextoptionsbuilder usepostgresql( [notnull] this abpdbcontextconfigurationcontext context, [canbenull] action<npgsqldbcontextoptionsbuilder> postgresqloptionsaction = null) { if (context.existingconnection != null) { return context.dbcontextoptions.usenpgsql(context.existingconnection, postgresqloptionsaction); } else { return context.dbcontextoptions.usenpgsql(context.connectionstring, postgresqloptionsaction); } } }
2.1.2 数据库上下文的配置工厂
无论是 preconfigure()
的委托集合,还是 configure()
配置的委托,都会在 dbcontextoptionsfactory
提供的 create<tdbcontext>(iserviceprovider serviceprovider)
方法中被使用。该方法的作用只有一个,执行框架的配置方法,然后生成数据库上下文的配置对象。
internal static class dbcontextoptionsfactory { public static dbcontextoptions<tdbcontext> create<tdbcontext>(iserviceprovider serviceprovider) where tdbcontext : abpdbcontext<tdbcontext> { // 获取一个 dbcontextcreationcontext 对象。 var creationcontext = getcreationcontext<tdbcontext>(serviceprovider); // 依据 creationcontext 信息构造一个配置上下文。 var context = new abpdbcontextconfigurationcontext<tdbcontext>( creationcontext.connectionstring, serviceprovider, creationcontext.connectionstringname, creationcontext.existingconnection ); // 获取 abpdboptions 配置。 var options = getdbcontextoptions<tdbcontext>(serviceprovider); // 从 options 当中获取添加的 preconfigure 与 configure 委托,并执行。 preconfigure(options, context); configure(options, context); // return context.dbcontextoptions.options; } // ... }
首先我们来看看 getcreationcontext<tdbcontext>()
方法是如何构造一个 dbcontextcreationcontext
对象的,它会优先从 current
取得一个上下文对象,如果存在则直接返回,不存在则使用连接字符串等信息构建一个新的上下文对象。
private static dbcontextcreationcontext getcreationcontext<tdbcontext>(iserviceprovider serviceprovider) where tdbcontext : abpdbcontext<tdbcontext> { // 优先从一个 asynclocal 当中获取。 var context = dbcontextcreationcontext.current; if (context != null) { return context; } // 从 tdbcontext 的 connectionstringname 特性获取连接字符串名称。 var connectionstringname = connectionstringnameattribute.getconnstringname<tdbcontext>(); // 使用 iconnectionstringresolver 根据指定的名称获得连接字符串。 var connectionstring = serviceprovider.getrequiredservice<iconnectionstringresolver>().resolve(connectionstringname); // 构造一个新的 dbcontextcreationcontext 对象。 return new dbcontextcreationcontext( connectionstringname, connectionstring ); }
2.1.3 连接字符串解析器
与老版本的 abp 一样,abp vnext 将连接字符串解析的工作,抽象了一个解析器。连接字符串解析器默认有两种实现,适用于普通系统和多租户系统。
普通的解析器,名字叫做 defaultconnectionstringresolver
,它的连接字符串都是从 abpdbconnectionoptions
当中获取的,而这个 option 最终是从 iconfiguration
映射过来的,一般来说就是你 appsetting.json
文件当中的连接字符串配置。
多租户解析器 的实现叫做 multitenantconnectionstringresolver
,它的内部核心逻辑就是获得到当前的租户,并查询租户所对应的连接字符串,这样就可以实现每个租户都拥有不同的数据库实例。
2.1.4 数据库上下文配置工厂的使用
回到最开始的地方,方法 create<tdbcontext>(iserviceprovider serviceprovider)
在什么地方会被使用呢?跳转到唯一的调用点是在 abpefcoreservicecollectionextensions
静态类的内部,它提供的 addabpdbcontext<tdbcontext>()
方法内部,就使用了 create<tdbcontext>()
作为 dbcontextoptions<tdbcontext>
的工厂方法。
public static class abpefcoreservicecollectionextensions { public static iservicecollection addabpdbcontext<tdbcontext>( this iservicecollection services, action<iabpdbcontextregistrationoptionsbuilder> optionsbuilder = null) where tdbcontext : abpdbcontext<tdbcontext> { services.addmemorycache(); // 构造一个数据库注册配置对象。 var options = new abpdbcontextregistrationoptions(typeof(tdbcontext), services); // 回调传入的委托。 optionsbuilder?.invoke(options); // 注入指定 tdbcontext 的 dboptions<tdbcontext> ,将会使用 create<tdbcontext> 方法进行瞬时对象构造。 services.tryaddtransient(dbcontextoptionsfactory.create<tdbcontext>); // 替换指定类型的 dbcontext 为当前 tdbcontext。 foreach (var dbcontexttype in options.replaceddbcontexttypes) { services.replace(servicedescriptor.transient(dbcontexttype, typeof(tdbcontext))); } // 构造 ef core 仓储注册器,并添加仓储。 new efcorerepositoryregistrar(options).addrepositories(); return services; } }
2.2 仓储的注入与实现
关于仓储的注入,其实在之前的文章就有讲过,这里我就大概说一下情况。
在上述代码当中,调用了 addabpdbcontext<tdbcontext>()
方法之后,就会通过 repository registrar 进行仓储注入。
public virtual void addrepositories() { // 遍历用户添加的自定义仓储。 foreach (var customrepository in options.customrepositories) { // 调用 adddefaultrepository() 方法注入仓储。 options.services.adddefaultrepository(customrepository.key, customrepository.value); } // 判断是否需要注册实体的默认仓储。 if (options.registerdefaultrepositories) { registerdefaultrepositories(); } }
可以看到,在注入仓储的时候,分为两种情况。第一种是用户的自定义仓储,这种仓储是通过 addrepository()
方法添加的,添加之后将会把它的 实体类型 与 仓储类型 放在一个字典内部。在仓储注册器进行初始化的时候,就会遍历这个字典,进行注入动作。
第二种情况则是用户在设置了 registerdefaultrepositories=true
的情况下,abp vnext 就会从数据库上下文的类型定义上遍历所有实体类型,然后进行默认仓储注册。
具体的仓储注册实现:
public static iservicecollection adddefaultrepository(this iservicecollection services, type entitytype, type repositoryimplementationtype) { // 注册 ireadonlybasicrepository<tentity>。 var readonlybasicrepositoryinterface = typeof(ireadonlybasicrepository<>).makegenerictype(entitytype); // 如果具体实现类型继承了该接口,则进行注入。 if (readonlybasicrepositoryinterface.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(readonlybasicrepositoryinterface, repositoryimplementationtype); // 注册 ireadonlyrepository<tentity>。 var readonlyrepositoryinterface = typeof(ireadonlyrepository<>).makegenerictype(entitytype); if (readonlyrepositoryinterface.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(readonlyrepositoryinterface, repositoryimplementationtype); } // 注册 ibasicrepository<tentity>。 var basicrepositoryinterface = typeof(ibasicrepository<>).makegenerictype(entitytype); if (basicrepositoryinterface.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(basicrepositoryinterface, repositoryimplementationtype); // 注册 irepository<tentity>。 var repositoryinterface = typeof(irepository<>).makegenerictype(entitytype); if (repositoryinterface.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(repositoryinterface, repositoryimplementationtype); } } } // 获得实体的主键类型,如果不存在则忽略。 var primarykeytype = entityhelper.findprimarykeytype(entitytype); if (primarykeytype != null) { // 注册 ireadonlybasicrepository<tentity, tkey>。 var readonlybasicrepositoryinterfacewithpk = typeof(ireadonlybasicrepository<,>).makegenerictype(entitytype, primarykeytype); if (readonlybasicrepositoryinterfacewithpk.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(readonlybasicrepositoryinterfacewithpk, repositoryimplementationtype); // 注册 ireadonlyrepository<tentity, tkey>。 var readonlyrepositoryinterfacewithpk = typeof(ireadonlyrepository<,>).makegenerictype(entitytype, primarykeytype); if (readonlyrepositoryinterfacewithpk.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(readonlyrepositoryinterfacewithpk, repositoryimplementationtype); } // 注册 ibasicrepository<tentity, tkey>。 var basicrepositoryinterfacewithpk = typeof(ibasicrepository<,>).makegenerictype(entitytype, primarykeytype); if (basicrepositoryinterfacewithpk.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(basicrepositoryinterfacewithpk, repositoryimplementationtype); // 注册 irepository<tentity, tkey>。 var repositoryinterfacewithpk = typeof(irepository<,>).makegenerictype(entitytype, primarykeytype); if (repositoryinterfacewithpk.isassignablefrom(repositoryimplementationtype)) { services.tryaddtransient(repositoryinterfacewithpk, repositoryimplementationtype); } } } } return services; }
回到仓储自动注册的地方,可以看到实现类型是由 getdefaultrepositoryimplementationtype()
方法提供的。
protected virtual void registerdefaultrepository(type entitytype) { options.services.adddefaultrepository( entitytype, getdefaultrepositoryimplementationtype(entitytype) ); } protected virtual type getdefaultrepositoryimplementationtype(type entitytype) { var primarykeytype = entityhelper.findprimarykeytype(entitytype); if (primarykeytype == null) { return options.specifieddefaultrepositorytypes ? options.defaultrepositoryimplementationtypewithoutkey.makegenerictype(entitytype) : getrepositorytype(options.defaultrepositorydbcontexttype, entitytype); } return options.specifieddefaultrepositorytypes ? options.defaultrepositoryimplementationtype.makegenerictype(entitytype, primarykeytype) : getrepositorytype(options.defaultrepositorydbcontexttype, entitytype, primarykeytype); } protected abstract type getrepositorytype(type dbcontexttype, type entitytype); protected abstract type getrepositorytype(type dbcontexttype, type entitytype, type primarykeytype);
这里的两个 getrepositorytype()
都是抽象方法,具体的实现分别在 efcorerepositoryregistrar
、memorydbrepositoryregistrar
、mongodbrepositoryregistrar
的内部,这里我们只讲 ef core 相关的。
protected override type getrepositorytype(type dbcontexttype, type entitytype) { return typeof(efcorerepository<,>).makegenerictype(dbcontexttype, entitytype); }
可以看到,在方法内部是构造了一个 efcorerepository
类型作为默认仓储的实现。
2.3 数据库上下文提供者
在 ef core 仓储的内部,需要操作数据库时,必须要获得一个数据库上下文。在仓储内部的数据库上下文都是由 idbcontextprovider<tdbcontext>
提供了,这个东西在 ef core 模块初始化的时候就已经被注册,它的默认实现是 unitofworkdbcontextprovider<tdbcontext>
。
public class efcorerepository<tdbcontext, tentity> : repositorybase<tentity>, iefcorerepository<tentity> where tdbcontext : iefcoredbcontext where tentity : class, ientity { public virtual dbset<tentity> dbset => dbcontext.set<tentity>(); dbcontext iefcorerepository<tentity>.dbcontext => dbcontext.as<dbcontext>(); protected virtual tdbcontext dbcontext => _dbcontextprovider.getdbcontext(); // ... private readonly idbcontextprovider<tdbcontext> _dbcontextprovider; // ... public efcorerepository(idbcontextprovider<tdbcontext> dbcontextprovider) { _dbcontextprovider = dbcontextprovider; // ... } // ... }
首先来看一下这个实现类的基本定义,比较简单,注入了两个接口,分别用于获取工作单元和构造 dbcontext
。需要注意的是,这里通过 where
约束来指定 tdbcontext
必须实现 iefcoredbcontext
接口。
public class unitofworkdbcontextprovider<tdbcontext> : idbcontextprovider<tdbcontext> where tdbcontext : iefcoredbcontext { private readonly iunitofworkmanager _unitofworkmanager; private readonly iconnectionstringresolver _connectionstringresolver; public unitofworkdbcontextprovider( iunitofworkmanager unitofworkmanager, iconnectionstringresolver connectionstringresolver) { _unitofworkmanager = unitofworkmanager; _connectionstringresolver = connectionstringresolver; } // ... }
接着想下看,接口只定义了一个方法,就是 getdbcontext()
,在这个默认实现里面,首先会从缓存里面获取数据库上下文,如果没有获取到,则创建一个新的数据库上下文。
public tdbcontext getdbcontext() { // 获得当前的可用工作单元。 var unitofwork = _unitofworkmanager.current; if (unitofwork == null) { throw new abpexception("a dbcontext can only be created inside a unit of work!"); } // 获得数据库连接上下文的连接字符串名称。 var connectionstringname = connectionstringnameattribute.getconnstringname<tdbcontext>(); // 根据名称解析具体的连接字符串。 var connectionstring = _connectionstringresolver.resolve(connectionstringname); // 构造数据库上下文缓存 key。 var dbcontextkey = $"{typeof(tdbcontext).fullname}_{connectionstring}"; // 从工作单元的缓存当中获取数据库上下文,不存在则调用 createdbcontext() 创建。 var databaseapi = unitofwork.getoradddatabaseapi( dbcontextkey, () => new efcoredatabaseapi<tdbcontext>( createdbcontext(unitofwork, connectionstringname, connectionstring) )); return ((efcoredatabaseapi<tdbcontext>)databaseapi).dbcontext; }
回到最开始的数据库上下文配置工厂,在它的内部会优先从一个 current
获取一个 dbcontextcreationcontext
实例。而在这里,就是 current
被赋值的地方,只要调用了 use()
方法,在释放之前都会获取到同一个实例。
private tdbcontext createdbcontext(iunitofwork unitofwork, string connectionstringname, string connectionstring) { var creationcontext = new dbcontextcreationcontext(connectionstringname, connectionstring); using (dbcontextcreationcontext.use(creationcontext)) { // 这里是重点,真正创建数据库上下文的地方。 var dbcontext = createdbcontext(unitofwork); if (unitofwork.options.timeout.hasvalue && dbcontext.database.isrelational() && !dbcontext.database.getcommandtimeout().hasvalue) { dbcontext.database.setcommandtimeout(unitofwork.options.timeout.value.totalseconds.to<int>()); } return dbcontext; } } // 如果是事务型的工作单元,则调用 createdbcontextwithtransaction() 进行创建,但不论如何都是通过工作单元提供的 iserviceprovider 解析出来 dbcontext 的。 private tdbcontext createdbcontext(iunitofwork unitofwork) { return unitofwork.options.istransactional ? createdbcontextwithtransaction(unitofwork) : unitofwork.serviceprovider.getrequiredservice<tdbcontext>(); }
以下代码才是在真正地创建 dbcontext
实例。
public tdbcontext createdbcontextwithtransaction(iunitofwork unitofwork) { var transactionapikey = $"entityframeworkcore_{dbcontextcreationcontext.current.connectionstring}"; var activetransaction = unitofwork.findtransactionapi(transactionapikey) as efcoretransactionapi; // 没有取得缓存。 if (activetransaction == null) { var dbcontext = unitofwork.serviceprovider.getrequiredservice<tdbcontext>(); // 判断是否指定了事务隔离级别,并开始事务。 var dbtransaction = unitofwork.options.isolationlevel.hasvalue ? dbcontext.database.begintransaction(unitofwork.options.isolationlevel.value) : dbcontext.database.begintransaction(); // 跟工作单元绑定添加一个已经激活的事务。 unitofwork.addtransactionapi( transactionapikey, new efcoretransactionapi( dbtransaction, dbcontext ) ); // 返回构造好的数据库上下文。 return dbcontext; } else { dbcontextcreationcontext.current.existingconnection = activetransaction.dbcontexttransaction.getdbtransaction().connection; var dbcontext = unitofwork.serviceprovider.getrequiredservice<tdbcontext>(); if (dbcontext.as<dbcontext>().hasrelationaltransactionmanager()) { dbcontext.database.usetransaction(activetransaction.dbcontexttransaction.getdbtransaction()); } else { dbcontext.database.begintransaction(); //todo: why not using the new created transaction? } activetransaction.attendeddbcontexts.add(dbcontext); return dbcontext; } }
2.4 数据过滤器
abp vnext 还提供了数据过滤器机制,可以让你根据指定的标识过滤数据,例如租户 id 和软删除标记。它的基本接口定义在 volo.abp.data 项目的 idatafilter.cs
文件中,提供了启用、禁用、检测方法。
public interface idatafilter<tfilter> where tfilter : class { idisposable enable(); idisposable disable(); bool isenabled { get; } } public interface idatafilter { idisposable enable<tfilter>() where tfilter : class; idisposable disable<tfilter>() where tfilter : class; bool isenabled<tfilter>() where tfilter : class; }
默认实现也在该项目下面的 datafilter.cs
文件,首先看以下 idatafilter
的默认实现 datafilter
,内部有一个解析器和并发字典。这个并发字典存储了所有的过滤器,其键是真实过滤器的类型(isoftdelete
或 imultitenant
),值是 datafilter<tfilter>
,具体对象根据 tfilter
的不同而不同。
public class datafilter : idatafilter, isingletondependency { private readonly concurrentdictionary<type, object> _filters; private readonly iserviceprovider _serviceprovider; public datafilter(iserviceprovider serviceprovider) { _serviceprovider = serviceprovider; _filters = new concurrentdictionary<type, object>(); } // ... }
看一下其他的方法,都是对 idatafilter<filter>
的包装。
public class datafilter : idatafilter, isingletondependency { // ... public idisposable enable<tfilter>() where tfilter : class { return getfilter<tfilter>().enable(); } public idisposable disable<tfilter>() where tfilter : class { return getfilter<tfilter>().disable(); } public bool isenabled<tfilter>() where tfilter : class { return getfilter<tfilter>().isenabled; } private idatafilter<tfilter> getfilter<tfilter>() where tfilter : class { // 并发字典当中获取指定类型的过滤器,如果不存在则从 ioc 中解析。 return _filters.getoradd( typeof(tfilter), () => _serviceprovider.getrequiredservice<idatafilter<tfilter>>() ) as idatafilter<tfilter>; } }
这么看来,idatafilter
叫做 idatafiltermanager
更加合适一点,最开始我还没搞明白两个接口和实现的区别,真正搞事情的是 datafilter<filter>
。
public class datafilter<tfilter> : idatafilter<tfilter> where tfilter : class { public bool isenabled { get { ensureinitialized(); return _filter.value.isenabled; } } // 注入数据过滤器配置类。 private readonly abpdatafilteroptions _options; // 用于存储过滤器的启用状态。 private readonly asynclocal<datafilterstate> _filter; public datafilter(ioptions<abpdatafilteroptions> options) { _options = options.value; _filter = new asynclocal<datafilterstate>(); } // ... // 确保初始化成功。 private void ensureinitialized() { if (_filter.value != null) { return; } // 如果过滤器的默认状态为 null,优先从配置类中取得指定过滤器的默认启用状态,如果不存在则默认为启用。 _filter.value = _options.defaultstates.getordefault(typeof(tfilter))?.clone() ?? new datafilterstate(true); } }
数据过滤器在设计的时候,也是按照工作单元的形式进行设计的。不论是启用还是停用都是范围性的,会返回一个用 disposeaction
包装的可释放对象,这样在离开 using
语句块的时候,就会还原为来的状态。比如调用 enable()
方法,在离开 using
语句块之后,会调用 disable()
禁用掉数据过滤器。
public idisposable enable() { if (isenabled) { return nulldisposable.instance; } _filter.value.isenabled = true; return new disposeaction(() => disable()); } public idisposable disable() { if (!isenabled) { return nulldisposable.instance; } _filter.value.isenabled = false; return new disposeaction(() => enable()); }
2.4.1 mongodb 与 memory 的集成
可以看到有两处使用,分别是 volo.abp.domain 项目与 volo.abp.entityframeworkcore 项目。
首先看第一个项目的用法:
public abstract class repositorybase<tentity> : basicrepositorybase<tentity>, irepository<tentity> where tentity : class, ientity { public idatafilter datafilter { get; set; } // ... // 分别在查询的时候判断实体是否实现了两个接口。 protected virtual tqueryable applydatafilters<tqueryable>(tqueryable query) where tqueryable : iqueryable<tentity> { // 如果实现了软删除接口,则从 datafilter 中获取过滤器的开启状态。 // 如果已经开启,则过滤掉被删除的数据。 if (typeof(isoftdelete).isassignablefrom(typeof(tentity))) { query = (tqueryable)query.whereif(datafilter.isenabled<isoftdelete>(), e => ((isoftdelete)e).isdeleted == false); } // 如果实现了多租户接口,则从 datafilter 中获取过滤器的开启状态。 // 如果已经开启,则按照租户 id 过滤数据。 if (typeof(imultitenant).isassignablefrom(typeof(tentity))) { var tenantid = currenttenant.id; query = (tqueryable)query.whereif(datafilter.isenabled<imultitenant>(), e => ((imultitenant)e).tenantid == tenantid); } return query; } // ... }
逻辑比较简单,都是判断实体是否实现某个接口,并且结合启用状态来进行过滤,在原有 iquerable
拼接 whereif()
即可。但是 ef core 使用这种方式不行,所以上述方法只会在 memory 和 mongodb 有使用。
2.4.2 ef core 的集成
ef core 集成数据过滤器则是放在数据库上下文基类 abpdbcontext<tdbcontext>
中,在数据库上下文的 onmodelcreating()
方法内通过 configurebasepropertiesmethodinfo
进行反射调用。
public abstract class abpdbcontext<tdbcontext> : dbcontext, iefcoredbcontext, itransientdependency where tdbcontext : dbcontext { // ... protected virtual bool ismultitenantfilterenabled => datafilter?.isenabled<imultitenant>() ?? false; protected virtual bool issoftdeletefilterenabled => datafilter?.isenabled<isoftdelete>() ?? false; // ... public idatafilter datafilter { get; set; } // ... private static readonly methodinfo configurebasepropertiesmethodinfo = typeof(abpdbcontext<tdbcontext>) .getmethod( nameof(configurebaseproperties), bindingflags.instance | bindingflags.nonpublic ); // ... protected override void onmodelcreating(modelbuilder modelbuilder) { base.onmodelcreating(modelbuilder); foreach (var entitytype in modelbuilder.model.getentitytypes()) { configurebasepropertiesmethodinfo .makegenericmethod(entitytype.clrtype) .invoke(this, new object[] { modelbuilder, entitytype }); // ... } } // ... protected virtual void configurebaseproperties<tentity>(modelbuilder modelbuilder, imutableentitytype mutableentitytype) where tentity : class { if (mutableentitytype.isowned()) { return; } configureconcurrencystampproperty<tentity>(modelbuilder, mutableentitytype); configureextraproperties<tentity>(modelbuilder, mutableentitytype); configureauditproperties<tentity>(modelbuilder, mutableentitytype); configuretenantidproperty<tentity>(modelbuilder, mutableentitytype); // 在这里,配置全局过滤器。 configureglobalfilters<tentity>(modelbuilder, mutableentitytype); } // ... protected virtual void configureglobalfilters<tentity>(modelbuilder modelbuilder, imutableentitytype mutableentitytype) where tentity : class { // 符合条件则为其创建过滤表达式。 if (mutableentitytype.basetype == null && shouldfilterentity<tentity>(mutableentitytype)) { // 创建过滤表达式。 var filterexpression = createfilterexpression<tentity>(); if (filterexpression != null) { // 为指定的实体配置查询过滤器。 modelbuilder.entity<tentity>().hasqueryfilter(filterexpression); } } } // ... // 判断实体是否拥有过滤器。 protected virtual bool shouldfilterentity<tentity>(imutableentitytype entitytype) where tentity : class { if (typeof(imultitenant).isassignablefrom(typeof(tentity))) { return true; } if (typeof(isoftdelete).isassignablefrom(typeof(tentity))) { return true; } return false; } // 构建表达式。 protected virtual expression<func<tentity, bool>> createfilterexpression<tentity>() where tentity : class { expression<func<tentity, bool>> expression = null; if (typeof(isoftdelete).isassignablefrom(typeof(tentity))) { expression = e => !issoftdeletefilterenabled || !ef.property<bool>(e, "isdeleted"); } if (typeof(imultitenant).isassignablefrom(typeof(tentity))) { expression<func<tentity, bool>> multitenantfilter = e => !ismultitenantfilterenabled || ef.property<guid>(e, "tenantid") == currenttenantid; expression = expression == null ? multitenantfilter : combineexpressions(expression, multitenantfilter); } return expression; } // ... }
2.5 领域事件集成
在讲解事件总线与 ddd 这块的时候,我有提到过 abp vnext 有实现领域事件功能,用户可以在聚合根内部使用 addlocalevent(object eventdata)
或 adddistributedevent(object eventdata)
添加了领域事件。
public abstract class aggregateroot : entity, iaggregateroot, igeneratesdomainevents, ihasextraproperties, ihasconcurrencystamp { // ... private readonly icollection<object> _localevents = new collection<object>(); private readonly icollection<object> _distributedevents = new collection<object>(); // ... // 添加本地事件。 protected virtual void addlocalevent(object eventdata) { _localevents.add(eventdata); } // 添加分布式事件。 protected virtual void adddistributedevent(object eventdata) { _distributedevents.add(eventdata); } // 获得所有本地事件。 public virtual ienumerable<object> getlocalevents() { return _localevents; } // 获得所有分布式事件。 public virtual ienumerable<object> getdistributedevents() { return _distributedevents; } // 清空聚合需要触发的所有本地事件。 public virtual void clearlocalevents() { _localevents.clear(); } // 清空聚合需要触发的所有分布式事件。 public virtual void cleardistributedevents() { _distributedevents.clear(); } }
可以看到,我们在聚合内部执行任何业务行为的时候,可以通过上述的方法发送领域事件。那这些事件是在什么时候被发布的呢?
发现这几个 get 方法有被 abpdbcontext
所调用,其实在它的内部,会在每次 savechangesasync()
的时候,遍历所有实体,并获取它们的本地事件与分布式事件集合,最后由 entitychangeeventhelper
进行触发。
public abstract class abpdbcontext<tdbcontext> : dbcontext, iefcoredbcontext, itransientdependency where tdbcontext : dbcontext { // ... public override async task<int> savechangesasync(bool acceptallchangesonsuccess, cancellationtoken cancellationtoken = default) { try { var auditlog = auditingmanager?.current?.log; list<entitychangeinfo> entitychangelist = null; if (auditlog != null) { entitychangelist = entityhistoryhelper.createchangelist(changetracker.entries().tolist()); } var changereport = applyabpconcepts(); var result = await base.savechangesasync(acceptallchangesonsuccess, cancellationtoken).configureawait(false); // 触发领域事件。 await entitychangeeventhelper.triggereventsasync(changereport).configureawait(false); if (auditlog != null) { entityhistoryhelper.updatechangelist(entitychangelist); auditlog.entitychanges.addrange(entitychangelist); logger.logdebug($"added {entitychangelist.count} entity changes to the current audit log"); } return result; } catch (dbupdateconcurrencyexception ex) { throw new abpdbconcurrencyexception(ex.message, ex); } finally { changetracker.autodetectchangesenabled = true; } } // ... protected virtual entitychangereport applyabpconcepts() { var changereport = new entitychangereport(); // 遍历所有的实体变更事件。 foreach (var entry in changetracker.entries().tolist()) { applyabpconcepts(entry, changereport); } return changereport; } protected virtual void applyabpconcepts(entityentry entry, entitychangereport changereport) { // 根据不同的实体操作状态,执行不同的操作。 switch (entry.state) { case entitystate.added: applyabpconceptsforaddedentity(entry, changereport); break; case entitystate.modified: applyabpconceptsformodifiedentity(entry, changereport); break; case entitystate.deleted: applyabpconceptsfordeletedentity(entry, changereport); break; } // 添加领域事件。 adddomainevents(changereport, entry.entity); } // ... protected virtual void adddomainevents(entitychangereport changereport, object entityasobj) { var generatesdomaineventsentity = entityasobj as igeneratesdomainevents; if (generatesdomaineventsentity == null) { return; } // 获取到所有的本地事件和分布式事件,将其加入到 entitychangereport 对象当中。 var localevents = generatesdomaineventsentity.getlocalevents()?.toarray(); if (localevents != null && localevents.any()) { changereport.domainevents.addrange(localevents.select(eventdata => new domainevententry(entityasobj, eventdata))); generatesdomaineventsentity.clearlocalevents(); } var distributedevents = generatesdomaineventsentity.getdistributedevents()?.toarray(); if (distributedevents != null && distributedevents.any()) { changereport.distributedevents.addrange(distributedevents.select(eventdata => new domainevententry(entityasobj, eventdata))); generatesdomaineventsentity.cleardistributedevents(); } } }
转到 `` 的内部,发现有如下代码:
// ... public async task triggereventsasync(entitychangereport changereport) { // 触发领域事件。 await triggereventsinternalasync(changereport).configureawait(false); if (changereport.isempty() || unitofworkmanager.current == null) { return; } await unitofworkmanager.current.savechangesasync().configureawait(false); } protected virtual async task triggereventsinternalasync(entitychangereport changereport) { // 触发默认的实体变更事件,例如某个实体被创建、修改、删除。 await triggerentitychangeevents(changereport.changedentities).configureawait(false); // 触发用户自己发送的领域事件。 await triggerlocalevents(changereport.domainevents).configureawait(false); await triggerdistributedevents(changereport.distributedevents).configureawait(false); } // ... protected virtual async task triggerlocalevents(list<domainevententry> localevents) { foreach (var localevent in localevents) { await localeventbus.publishasync(localevent.eventdata.gettype(), localevent.eventdata).configureawait(false); } } protected virtual async task triggerdistributedevents(list<domainevententry> distributedevents) { foreach (var distributedevent in distributedevents) { await distributedeventbus.publishasync(distributedevent.eventdata.gettype(), distributedevent.eventdata).configureawait(false); } }
三、系列文章目录
跳转到文章总目录。
上一篇: C# WPF 时钟动画(1/2)
推荐阅读
-
[Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)
-
[Abp vNext 源码分析] - 11. 用户的自定义参数与配置
-
[Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)
-
[Abp vNext 源码分析] - 9. 接口参数的验证
-
[Abp vNext 源码分析] - 2. 模块系统的变化
-
[Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)
-
[Abp vNext 源码分析] - 14. EntityFramework Core 的集成
-
[Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)
-
[Abp vNext 源码分析] - 9. 接口参数的验证
-
[Abp vNext 源码分析] - 11. 用户的自定义参数与配置