[Abp vNext 原始碼分析] - 14. EntityFramework Core 的整合
一、簡要介紹
在以前的文章裡面,我們介紹了 ABP vNext 在 DDD 模組定義了倉儲的介面定義和基本實現。本章將會介紹,ABP vNext 是如何將 EntityFramework Core 框架跟倉儲進行深度整合。
ABP vNext 在整合 EF Core 的時候,不只是簡單地實現了倉儲模式,除開倉儲以外,還提供了一系列的基礎設施,如領域事件的釋出,資料過濾器的實現。
二、原始碼分析
EntityFrameworkCore 相關的模組基本就下面幾個,除了第一個是核心 EntityFrameworkCore 模組以外,其他幾個都是封裝的 EntityFrameworkCore Provider,方便各種資料庫進行整合。
2.1 EF Core 模組整合與初始化
首先從 Volo.Abp.EntityFrameworkCore 的 AbpEntityFrameworkCoreModule
開始分析,該模組只重寫了 ConfigureServices()
方法,在內部也只有兩句程式碼。
public override void ConfigureServices(ServiceConfigurationContext context) { // 呼叫 AbpDbContextOptions 的預配置方法,為了解決下面的問題。 // https://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()
都是抽象方法,具體的實現分別在 EfCoreRepositoryRegistrar
、MemoryDbRepositoryRegistrar
、MongoDbRepositoryRegistrar
的內部,這裡我們只講 EF Core 相關的。
protected override Type GetRepositoryType(Type dbContextType, Type entityType)
{
return typeof(EfCoreRepository<,>).MakeGenericType(dbContextType, entityType);
}
可以看到,在方法內部是構造了一個 EfCoreRepository
型別作為預設倉儲的實現。
2.3 資料庫上下文提供者
在 Ef Core 倉儲的內部,需要操作資料庫時,必須要獲得一個數據庫上下文。在倉儲內部的資料庫上下文都是由 IDbContextProvider<TDbContext>
提供了,這個東西在 EF Core 模組初始化的時候就已經被註冊,它的預設實現是 UnitOfWorkDbContextProvider<TDbContext>
。
public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity>
where TDbContext : IEfCoreDbContext
where TEntity : class, IEntity
{
public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>();
DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>();
protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext();
// ...
private readonly IDbContextProvider<TDbContext> _dbContextProvider;
// ...
public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
// ...
}
// ...
}
首先來看一下這個實現類的基本定義,比較簡單,注入了兩個介面,分別用於獲取工作單元和構造 DbContext
。需要注意的是,這裡通過 where
約束來指定 TDbContext
必須實現 IEfCoreDbContext
介面。
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext>
where TDbContext : IEfCoreDbContext
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IConnectionStringResolver _connectionStringResolver;
public UnitOfWorkDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver)
{
_unitOfWorkManager = unitOfWorkManager;
_connectionStringResolver = connectionStringResolver;
}
// ...
}
接著想下看,介面只定義了一個方法,就是 GetDbContext()
,在這個預設實現裡面,首先會從快取裡面獲取資料庫上下文,如果沒有獲取到,則建立一個新的資料庫上下文。
public TDbContext GetDbContext()
{
// 獲得當前的可用工作單元。
var unitOfWork = _unitOfWorkManager.Current;
if (unitOfWork == null)
{
throw new AbpException("A DbContext can only be created inside a unit of work!");
}
// 獲得資料庫連線上下文的連線字串名稱。
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
// 根據名稱解析具體的連線字串。
var connectionString = _connectionStringResolver.Resolve(connectionStringName);
// 構造資料庫上下文快取 Key。
var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";
// 從工作單元的快取當中獲取資料庫上下文,不存在則呼叫 CreateDbContext() 建立。
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
() => new EfCoreDatabaseApi<TDbContext>(
CreateDbContext(unitOfWork, connectionStringName, connectionString)
));
return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext;
}
回到最開始的資料庫上下文配置工廠,在它的內部會優先從一個 Current
獲取一個 DbContextCreationContext
例項。而在這裡,就是 Current
被賦值的地方,只要呼叫了 Use()
方法,在釋放之前都會獲取到同一個例項。
private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
{
var creationContext = new DbContextCreationContext(connectionStringName, connectionString);
using (DbContextCreationContext.Use(creationContext))
{
// 這裡是重點,真正建立資料庫上下文的地方。
var dbContext = CreateDbContext(unitOfWork);
if (unitOfWork.Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To<int>());
}
return dbContext;
}
}
// 如果是事務型的工作單元,則呼叫 CreateDbContextWithTransaction() 進行建立,但不論如何都是通過工作單元提供的 IServiceProvider 解析出來 DbContext 的。
private TDbContext CreateDbContext(IUnitOfWork unitOfWork)
{
return unitOfWork.Options.IsTransactional
? CreateDbContextWithTransaction(unitOfWork)
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
}
以下程式碼才是在真正地建立 DbContext
例項。
public TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork)
{
var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}";
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi;
// 沒有取得快取。
if (activeTransaction == null)
{
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
// 判斷是否指定了事務隔離級別,並開始事務。
var dbtransaction = unitOfWork.Options.IsolationLevel.HasValue
? dbContext.Database.BeginTransaction(unitOfWork.Options.IsolationLevel.Value)
: dbContext.Database.BeginTransaction();
// 跟工作單元繫結新增一個已經啟用的事務。
unitOfWork.AddTransactionApi(
transactionApiKey,
new EfCoreTransactionApi(
dbtransaction,
dbContext
)
);
// 返回構造好的資料庫上下文。
return dbContext;
}
else
{
DbContextCreationContext.Current.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection;
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
if (dbContext.As<DbContext>().HasRelationalTransactionManager())
{
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
}
else
{
dbContext.Database.BeginTransaction(); //TODO: Why not using the new created transaction?
}
activeTransaction.AttendedDbContexts.Add(dbContext);
return dbContext;
}
}
2.4 資料過濾器
ABP vNext 還提供了資料過濾器機制,可以讓你根據指定的標識過濾資料,例如租戶 Id 和軟刪除標記。它的基本介面定義在 Volo.Abp.Data 專案的 IDataFilter.cs
檔案中,提供了啟用、禁用、檢測方法。
public interface IDataFilter<TFilter>
where TFilter : class
{
IDisposable Enable();
IDisposable Disable();
bool IsEnabled { get; }
}
public interface IDataFilter
{
IDisposable Enable<TFilter>()
where TFilter : class;
IDisposable Disable<TFilter>()
where TFilter : class;
bool IsEnabled<TFilter>()
where TFilter : class;
}
預設實現也在該專案下面的 DataFilter.cs
檔案,首先看以下 IDataFilter
的預設實現 DataFilter
,內部有一個解析器和併發字典。這個併發字典儲存了所有的過濾器,其鍵是真實過濾器的型別(ISoftDelete
或 IMultiTenant
),值是 DataFilter<TFilter>
,具體物件根據 TFilter
的不同而不同。
public class DataFilter : IDataFilter, ISingletonDependency
{
private readonly ConcurrentDictionary<Type, object> _filters;
private readonly IServiceProvider _serviceProvider;
public DataFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_filters = new ConcurrentDictionary<Type, object>();
}
// ...
}
看一下其他的方法,都是對 IDataFilter<Filter>
的包裝。
public class DataFilter : IDataFilter, ISingletonDependency
{
// ...
public IDisposable Enable<TFilter>()
where TFilter : class
{
return GetFilter<TFilter>().Enable();
}
public IDisposable Disable<TFilter>()
where TFilter : class
{
return GetFilter<TFilter>().Disable();
}
public bool IsEnabled<TFilter>()
where TFilter : class
{
return GetFilter<TFilter>().IsEnabled;
}
private IDataFilter<TFilter> GetFilter<TFilter>()
where TFilter : class
{
// 併發字典當中獲取指定型別的過濾器,如果不存在則從 IoC 中解析。
return _filters.GetOrAdd(
typeof(TFilter),
() => _serviceProvider.GetRequiredService<IDataFilter<TFilter>>()
) as IDataFilter<TFilter>;
}
}
這麼看來,IDataFilter
叫做 IDataFilterManager
更加合適一點,最開始我還沒搞明白兩個介面和實現的區別,真正搞事情的是 DataFilter<Filter>
。
public class DataFilter<TFilter> : IDataFilter<TFilter>
where TFilter : class
{
public bool IsEnabled
{
get
{
EnsureInitialized();
return _filter.Value.IsEnabled;
}
}
// 注入資料過濾器配置類。
private readonly AbpDataFilterOptions _options;
// 用於儲存過濾器的啟用狀態。
private readonly AsyncLocal<DataFilterState> _filter;
public DataFilter(IOptions<AbpDataFilterOptions> options)
{
_options = options.Value;
_filter = new AsyncLocal<DataFilterState>();
}
// ...
// 確保初始化成功。
private void EnsureInitialized()
{
if (_filter.Value != null)
{
return;
}
// 如果過濾器的預設狀態為 NULL,優先從配置類中取得指定過濾器的預設啟用狀態,如果不存在則預設為啟用。
_filter.Value = _options.DefaultStates.GetOrDefault(typeof(TFilter))?.Clone() ?? new DataFilterState(true);
}
}
資料過濾器在設計的時候,也是按照工作單元的形式進行設計的。不論是啟用還是停用都是範圍性的,會返回一個用 DisposeAction
包裝的可釋放物件,這樣在離開 using
語句塊的時候,就會還原為來的狀態。比如呼叫 Enable()
方法,在離開 using
語句塊之後,會呼叫 Disable()
禁用掉資料過濾器。
public IDisposable Enable()
{
if (IsEnabled)
{
return NullDisposable.Instance;
}
_filter.Value.IsEnabled = true;
return new DisposeAction(() => Disable());
}
public IDisposable Disable()
{
if (!IsEnabled)
{
return NullDisposable.Instance;
}
_filter.Value.IsEnabled = false;
return new DisposeAction(() => Enable());
}
2.4.1 MongoDb 與 Memory 的整合
可以看到有兩處使用,分別是 Volo.Abp.Domain 專案與 Volo.Abp.EntityFrameworkCore 專案。
首先看第一個專案的用法:
public abstract class RepositoryBase<TEntity> : BasicRepositoryBase<TEntity>, IRepository<TEntity>
where TEntity : class, IEntity
{
public IDataFilter DataFilter { get; set; }
// ...
// 分別在查詢的時候判斷實體是否實現了兩個介面。
protected virtual TQueryable ApplyDataFilters<TQueryable>(TQueryable query)
where TQueryable : IQueryable<TEntity>
{
// 如果實現了軟刪除介面,則從 DataFilter 中獲取過濾器的開啟狀態。
// 如果已經開啟,則過濾掉被刪除的資料。
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
query = (TQueryable)query.WhereIf(DataFilter.IsEnabled<ISoftDelete>(), e => ((ISoftDelete)e).IsDeleted == false);
}
// 如果實現了多租戶介面,則從 DataFilter 中獲取過濾器的開啟狀態。
// 如果已經開啟,則按照租戶 Id 過濾資料。
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
var tenantId = CurrentTenant.Id;
query = (TQueryable)query.WhereIf(DataFilter.IsEnabled<IMultiTenant>(), e => ((IMultiTenant)e).TenantId == tenantId);
}
return query;
}
// ...
}
邏輯比較簡單,都是判斷實體是否實現某個介面,並且結合啟用狀態來進行過濾,在原有 IQuerable
拼接 WhereIf()
即可。但是 EF Core 使用這種方式不行,所以上述方法只會在 Memory 和 MongoDb 有使用。
2.4.2 EF Core 的整合
EF Core 整合資料過濾器則是放在資料庫上下文基類 AbpDbContext<TDbContext>
中,在資料庫上下文的 OnModelCreating()
方法內通過 ConfigureBasePropertiesMethodInfo
進行反射呼叫。
public abstract class AbpDbContext<TDbContext> : DbContext, IEfCoreDbContext, ITransientDependency
where TDbContext : DbContext
{
// ...
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
// ...
public IDataFilter DataFilter { get; set; }
// ...
private static readonly MethodInfo ConfigureBasePropertiesMethodInfo = typeof(AbpDbContext<TDbContext>)
.GetMethod(
nameof(ConfigureBaseProperties),
BindingFlags.Instance | BindingFlags.NonPublic
);
// ...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
ConfigureBasePropertiesMethodInfo
.MakeGenericMethod(entityType.ClrType)
.Invoke(this, new object[] { modelBuilder, entityType });
// ...
}
}
// ...
protected virtual void ConfigureBaseProperties<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
where TEntity : class
{
if (mutableEntityType.IsOwned())
{
return;
}
ConfigureConcurrencyStampProperty<TEntity>(modelBuilder, mutableEntityType);
ConfigureExtraProperties<TEntity>(modelBuilder, mutableEntityType);
ConfigureAuditProperties<TEntity>(modelBuilder, mutableEntityType);
ConfigureTenantIdProperty<TEntity>(modelBuilder, mutableEntityType);
// 在這裡,配置全域性過濾器。
ConfigureGlobalFilters<TEntity>(modelBuilder, mutableEntityType);
}
// ...
protected virtual void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
where TEntity : class
{
// 符合條件則為其建立過濾表示式。
if (mutableEntityType.BaseType == null && ShouldFilterEntity<TEntity>(mutableEntityType))
{
// 建立過濾表示式。
var filterExpression = CreateFilterExpression<TEntity>();
if (filterExpression != null)
{
// 為指定的實體配置查詢過濾器。
modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
}
}
}
// ...
// 判斷實體是否擁有過濾器。
protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
{
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return false;
}
// 構建表示式。
protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
where TEntity : class
{
Expression<Func<TEntity, bool>> expression = null;
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted");
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId;
expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter);
}
return expression;
}
// ...
}
2.5 領域事件整合
在講解事件匯流排與 DDD 這塊的時候,我有提到過 ABP vNext 有實現領域事件功能,使用者可以在聚合根內部使用 AddLocalEvent(object eventData)
或 AddDistributedEvent(object eventData)
添加了領域事件。
public abstract class AggregateRoot : Entity,
IAggregateRoot,
IGeneratesDomainEvents,
IHasExtraProperties,
IHasConcurrencyStamp
{
// ...
private readonly ICollection<object> _localEvents = new Collection<object>();
private readonly ICollection<object> _distributedEvents = new Collection<object>();
// ...
// 新增本地事件。
protected virtual void AddLocalEvent(object eventData)
{
_localEvents.Add(eventData);
}
// 新增分散式事件。
protected virtual void AddDistributedEvent(object eventData)
{
_distributedEvents.Add(eventData);
}
// 獲得所有本地事件。
public virtual IEnumerable<object> GetLocalEvents()
{
return _localEvents;
}
// 獲得所有分散式事件。
public virtual IEnumerable<object> GetDistributedEvents()
{
return _distributedEvents;
}
// 清空聚合需要觸發的所有本地事件。
public virtual void ClearLocalEvents()
{
_localEvents.Clear();
}
// 清空聚合需要觸發的所有分散式事件。
public virtual void ClearDistributedEvents()
{
_distributedEvents.Clear();
}
}
可以看到,我們在聚合內部執行任何業務行為的時候,可以通過上述的方法傳送領域事件。那這些事件是在什麼時候被髮布的呢?
發現這幾個 Get 方法有被 AbpDbContext
所呼叫,其實在它的內部,會在每次 SaveChangesAsync()
的時候,遍歷所有實體,並獲取它們的本地事件與分散式事件集合,最後由 EntityChangeEventHelper
進行觸發。
public abstract class AbpDbContext<TDbContext> : DbContext, IEfCoreDbContext, ITransientDependency
where TDbContext : DbContext
{
// ...
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
try
{
var auditLog = AuditingManager?.Current?.Log;
List<EntityChangeInfo> entityChangeList = null;
if (auditLog != null)
{
entityChangeList = EntityHistoryHelper.CreateChangeList(ChangeTracker.Entries().ToList());
}
var changeReport = ApplyAbpConcepts();
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken).ConfigureAwait(false);
// 觸發領域事件。
await EntityChangeEventHelper.TriggerEventsAsync(changeReport).ConfigureAwait(false);
if (auditLog != null)
{
EntityHistoryHelper.UpdateChangeList(entityChangeList);
auditLog.EntityChanges.AddRange(entityChangeList);
Logger.LogDebug($"Added {entityChangeList.Count} entity changes to the current audit log");
}
return result;
}
catch (DbUpdateConcurrencyException ex)
{
throw new AbpDbConcurrencyException(ex.Message, ex);
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
}
// ...
protected virtual EntityChangeReport ApplyAbpConcepts()
{
var changeReport = new EntityChangeReport();
// 遍歷所有的實體變更事件。
foreach (var entry in ChangeTracker.Entries().ToList())
{
ApplyAbpConcepts(entry, changeReport);
}
return changeReport;
}
protected virtual void ApplyAbpConcepts(EntityEntry entry, EntityChangeReport changeReport)
{
// 根據不同的實體操作狀態,執行不同的操作。
switch (entry.State)
{
case EntityState.Added:
ApplyAbpConceptsForAddedEntity(entry, changeReport);
break;
case EntityState.Modified:
ApplyAbpConceptsForModifiedEntity(entry, changeReport);
break;
case EntityState.Deleted:
ApplyAbpConceptsForDeletedEntity(entry, changeReport);
break;
}
// 新增領域事件。
AddDomainEvents(changeReport, entry.Entity);
}
// ...
protected virtual void AddDomainEvents(EntityChangeReport changeReport, object entityAsObj)
{
var generatesDomainEventsEntity = entityAsObj as IGeneratesDomainEvents;
if (generatesDomainEventsEntity == null)
{
return;
}
// 獲取到所有的本地事件和分散式事件,將其加入到 EntityChangeReport 物件當中。
var localEvents = generatesDomainEventsEntity.GetLocalEvents()?.ToArray();
if (localEvents != null && localEvents.Any())
{
changeReport.DomainEvents.AddRange(localEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData)));
generatesDomainEventsEntity.ClearLocalEvents();
}
var distributedEvents = generatesDomainEventsEntity.GetDistributedEvents()?.ToArray();
if (distributedEvents != null && distributedEvents.Any())
{
changeReport.DistributedEvents.AddRange(distributedEvents.Select(eventData => new DomainEventEntry(entityAsObj, eventData)));
generatesDomainEventsEntity.ClearDistributedEvents();
}
}
}
轉到 `` 的內部,發現有如下程式碼:
// ...
public async Task TriggerEventsAsync(EntityChangeReport changeReport)
{
// 觸發領域事件。
await TriggerEventsInternalAsync(changeReport).ConfigureAwait(false);
if (changeReport.IsEmpty() || UnitOfWorkManager.Current == null)
{
return;
}
await UnitOfWorkManager.Current.SaveChangesAsync().ConfigureAwait(false);
}
protected virtual async Task TriggerEventsInternalAsync(EntityChangeReport changeReport)
{
// 觸發預設的實體變更事件,例如某個實體被建立、修改、刪除。
await TriggerEntityChangeEvents(changeReport.ChangedEntities).ConfigureAwait(false);
// 觸發使用者自己傳送的領域事件。
await TriggerLocalEvents(changeReport.DomainEvents).ConfigureAwait(false);
await TriggerDistributedEvents(changeReport.DistributedEvents).ConfigureAwait(false);
}
// ...
protected virtual async Task TriggerLocalEvents(List<DomainEventEntry> localEvents)
{
foreach (var localEvent in localEvents)
{
await LocalEventBus.PublishAsync(localEvent.EventData.GetType(), localEvent.EventData).ConfigureAwait(false);
}
}
protected virtual async Task TriggerDistributedEvents(List<DomainEventEntry> distributedEvents)
{
foreach (var distributedEvent in distributedEvents)
{
await DistributedEventBus.PublishAsync(distributedEvent.EventData.GetType(), distributedEvent.EventData).ConfigureAwait(false);
}
}
三、系列文章目錄
點選我 跳轉到文章總目錄。