[Abp vNext source analysis] - 14. EntityFramework Core integration

First, a brief introduction

In previous articles inside, we introduced the ABP vNext in DDD module defines the basic storage interface definition and implementation. This chapter explains, ABP vNext is how EntityFramework Core framework for deep integration with the warehouse.

ABP vNext integrated EF Core when, not simply to achieve a storage mode, except for open storage, also provides a series of infrastructure, such as the release of the field events, data filter.

Second, source code analysis

EntityFrameworkCore related to the following several basic modules, except for the first module is the core EntityFrameworkCore than several other packages are EntityFrameworkCore Provider, various databases to facilitate integration.

2.1 EF Core module is integrated with the initialization

First, from Volo.Abp.EntityFrameworkCore the AbpEntityFrameworkCoreModulestart of the analysis, the module only rewrite the ConfigureServices()method, within only two codes.

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

First look at the first sentence of the code, it calls inside AbpDbContextOptionsprovided PreConfigure()methods. This method is very simple logic, will incoming Action<AbpDbContextConfigurationContext>commission added to a List<Action<AbpDbContextConfigurationContext>>collection, and DbContextOptionsFactorythe use of the plant.

Code for the second game is relatively simple, as the IDbContextProvider<>type of injection default implementation 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);
    }

    // ...
}


Can be seen from the above code, this AbpDbContextConfigurationContextis a configuration context for various ABP vNext frame disposed at initialization time.

2.1.1 EF Core Provider Integration

In the read AbpDbContextOptionstime code, I found that in addition to pre-configure, it provides a Configure([NotNull] Action<AbpDbContextConfigurationContext> action)method, as well as its generic overloads Configure<TDbContext>([NotNull] Action<AbpDbContextConfigurationContext<TDbContext>> action), their internal implementation and pre-configured similarly.

These two methods of ABP vNext within the application framework, the main module in each EF Provider among reflected.

I'm here to Volo.Abp.EntityFrameworkCore.PostgreSql module as an example, within the project only two classes defined extension methods. In AbpDbContextOptionsPostgreSqlExtensionswhich, on the use to the Configure()method.

public static void UsePostgreSql(
    [NotNull] this AbpDbContextOptions options,
    [CanBeNull] Action<NpgsqlDbContextOptionsBuilder> postgreSqlOptionsAction = null)
{
    options.Configure(context =>
    {
        // 这里的 context 类型是 AbpDbContextConfigurationContext。
        context.UsePostgreSql(postgreSqlOptionsAction);
    });
}

Code above UsePostgreSql()method is clearly not the extension method EF Core Provider defined jump to a specific implementation, it was found that one simple package. Due to AbpDbContextConfigurationContextinternal offers DbContextOptionsBuilder, so direct use of this DbContextOptionsBuilderextension is to provide a method call.

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 Configuration Database Context Factory

Whether it is PreConfigure()a collection of commissioned or Configure()commissioned configuration, it will be in the DbContextOptionsFactoryoffer Create<TDbContext>(IServiceProvider serviceProvider)are in use. The effect of the method is only one method of performing the frame configuration, and arranged to generate context object database.

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

    // ...
}

First we look at GetCreationContext<TDbContext>()how the method is to construct a DbContextCreationContexttarget, it will preferentially from Currentobtaining a context object, if there is a direct return, there is no connection string information is used to construct a new context and other objects.

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 connection string resolver

Like the old version of ABP, ABP vNext connect string parsing work, an abstract parser. There are two default connection string parser implementation for general systems and multi-tenant system.

普通的解析器,名字叫做 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);
    }
}

三、系列文章目录

点击我 跳转到文章总目录。

Guess you like

Origin www.cnblogs.com/myzony/p/12165354.html