当前位置: 移动技术网 > IT编程>开发语言>.net > [Abp vNext 源码分析] - 14. EntityFramework Core 的集成

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

2020年01月08日  | 移动技术网IT编程  | 我要评论

车标志图片大全,n0605,lanlanwg

一、简要介绍

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

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

二、源码分析

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

2.1 ef core 模块集成与初始化

首先从 volo.abp.entityframeworkcoreabpentityframeworkcoremodule 开始分析,该模块只重写了 configureservices() 方法,在内部也只有两句代码。

public override void configureservices(serviceconfigurationcontext context)
{
    // 调用 abpdbcontextoptions 的预配置方法,为了解决下面的问题。
    // https://stackoverflow.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() 都是抽象方法,具体的实现分别在 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 的集成

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

三、系列文章目录

跳转到文章总目录。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网