一、简要介绍

在以前的文章里面,我们介绍了 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);
}
}

三、系列文章目录

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

[Abp vNext 源码分析] - 14. EntityFramework Core 的集成的更多相关文章

  1. [Abp vNext 源码分析] - 文章目录

    一.简要介绍 ABP vNext 是 ABP 框架作者所发起的新项目,截止目前 (2019 年 2 月 18 日) 已经拥有 1400 多个 Star,最新版本号为 v 0.16.0 ,但还属于预览版 ...

  2. [Abp vNext 源码分析] - 5. DDD 的领域层支持(仓储、实体、值对象)

    一.简要介绍 ABP vNext 框架本身就是围绕着 DDD 理念进行设计的,所以在 DDD 里面我们能够见到的实体.仓储.值对象.领域服务,ABP vNext 框架都为我们进行了实现,这些基础设施都 ...

  3. [Abp vNext 源码分析] - 3. 依赖注入与拦截器

    一.简要说明 ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包.这里与原来的 ABP 框架 ...

  4. [Abp vNext 源码分析] - 2. 模块系统的变化

    一.简要说明 本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 I ...

  5. [Abp vNext 源码分析] - 1. 框架启动流程分析

    一.简要说明 本篇文章主要剖析与讲解 Abp vNext 在 Web API 项目下的启动流程,让大家了解整个 Abp vNext 框架是如何运作的.总的来说 ,Abp vNext 比起 ABP 框架 ...

  6. [Abp vNext 源码分析] - 4. 工作单元

    一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...

  7. [Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)

    一.简要介绍 ABP vNext 针对于应用服务层,为我们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块. PS:最近博主也是在恶补 DDD 相关的知识,这里推 ...

  8. [Abp vNext 源码分析] - 7. 权限与验证

    一.简要说明 在上篇文章里面,我们在 ApplicationService 当中看到了权限检测代码,通过注入 IAuthorizationService 就可以实现权限检测.不过跳转到源码才发现,这个 ...

  9. [Abp vNext 源码分析] - 9. 接口参数的验证

    一.简要说明 ABP vNext 当中的审计模块早在 依赖注入与拦截器一文中有所提及,但没有详细的对其进行分析. 审计模块是 ABP vNext 框架的一个基本组件,它能够提供一些实用日志记录.不过这 ...

随机推荐

  1. POJ2186 Popular Cows 题解 强连通分量入门题

    题目链接:http://poj.org/problem?id=2186 题目大意: 每头牛都想成为牛群中的红人. 给定N头牛的牛群和M个有序对(A, B),(A, B)表示牛A认为牛B是红人: 该关系 ...

  2. oracle避免在索引列上使用IS NULL和IS NOT NULL

    避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引 .对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空 ...

  3. Linux环境下第一次提交项目

    Linux环境下第一次提交项目: vi 日记 新增一个文件名为“日记”的文件 git status 工作区的状态 git add 日记 建立跟踪 git commit 提交变更 ----------- ...

  4. Python--day67--内容回顾

  5. [转]【译】.NET Core 3.0 中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web ...

  6. H3C 启动包过滤防火墙功能

  7. linux inode 结构

    inode 结构由内核在内部用来表示文件. 因此, 它和代表打开文件描述符的文件结构是不 同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构. ino ...

  8. UVa 1635 - Irrelevant Elements-[分解质因数]

    Young cryptoanalyst Georgie is investigating different schemes of generating random integer numbers ...

  9. H3C 路由器单跳操作

  10. linux进程简单睡眠

    当一个进程睡眠, 它这样做以期望某些条件在以后会成真. 如我们之前注意到的, 任何睡 眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真. Linux 内核中睡眠的 最简单方式是一个宏定义, ...