欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

程序员文章站 2022-06-15 12:02:34
一、简要介绍 在以前的文章里面,我们介绍了 ABP vNext 在 DDD 模块定义了仓储的接口定义和基本实现。本章将会介绍,ABP vNext 是如何将 EntityFramework Core 框架跟仓储进行深度集成。 ABP vNext 在集成 EF Core 的时候,不只是简单地实现了仓储模 ......

一、简要介绍

在以前的文章里面,我们介绍了 abp vnext 在 ddd 模块定义了仓储的接口定义和基本实现。本章将会介绍,abp vnext 是如何将 entityframework core 框架跟仓储进行深度集成。

abp vnext 在集成 ef core 的时候,不只是简单地实现了仓储模式,除开仓储以外,还提供了一系列的基础设施,如领域事件的发布,数据过滤器的实现。

二、源码分析

entityframeworkcore 相关的模块基本就下面几个,除了第一个是核心 entityframeworkcore 模块以外,其他几个都是封装的 entityframeworkcore provider,方便各种数据库进行集成。

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

2.1 ef core 模块集成与初始化

首先从 volo.abp.entityframeworkcoreabpentityframeworkcoremodule 开始分析,该模块只重写了 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);
    }

    // ...
}

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成


从上面的代码可以看出来,这个 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 模块当中有体现。

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

这里我以 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 将连接字符串解析的工作,抽象了一个解析器。连接字符串解析器默认有两种实现,适用于普通系统和多租户系统。

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

普通的解析器,名字叫做 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() 都是抽象方法,具体的实现分别在 efcorerepositoryregistrarmemorydbrepositoryregistrarmongodbrepositoryregistrar 的内部,这里我们只讲 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,内部有一个解析器和并发字典。这个并发字典存储了所有的过滤器,其键是真实过滤器的类型(isoftdeleteimultitenant),值是 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 的集成

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

可以看到有两处使用,分别是 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 有使用。

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

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();
    }
}

可以看到,我们在聚合内部执行任何业务行为的时候,可以通过上述的方法发送领域事件。那这些事件是在什么时候被发布的呢?

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成

发现这几个 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);
    }
}

三、系列文章目录

跳转到文章总目录。