在之前的文章裏面,咱們介紹了 ABP vNext 在 DDD 模塊定義了倉儲的接口定義和基本實現。本章將會介紹,ABP vNext 是如何將 EntityFramework Core 框架跟倉儲進行深度集成。html
ABP vNext 在集成 EF Core 的時候,不僅是簡單地實現了倉儲模式,除開倉儲之外,還提供了一系列的基礎設施,如領域事件的發佈,數據過濾器的實現。sql
EntityFrameworkCore 相關的模塊基本就下面幾個,除了第一個是核心 EntityFrameworkCore 模塊之外,其餘幾個都是封裝的 EntityFrameworkCore Provider,方便各類數據庫進行集成。mongodb
首先從 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
工廠中使用。express
第二局代碼則比較簡單,爲 IDbContextProvider<>
類型注入默認實現 UnitOfWorkDbContextProvider<>
。json
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 框架在初始化的時候進行各類配置。緩存
在翻閱 AbpDbContextOptions
代碼的時候,我發現除了預配置方法,它還提供了一個 Configure([NotNull] Action<AbpDbContextConfigurationContext> action)
方法,以及它的泛型重載 Configure<TDbContext>([NotNull] Action<AbpDbContextConfigurationContext<TDbContext>> action)
,它們的內部實現與預配置相似。併發
這兩個方法在 ABP vNext 框架內部的應用,主要在各個 EF Provider 模塊當中有體現。app
這裏我以 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); } } }
不管是 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 ); }
與老版本的 ABP 同樣,ABP vNext 將鏈接字符串解析的工做,抽象了一個解析器。鏈接字符串解析器默認有兩種實現,適用於普通系統和多租戶系統。
普通的解析器,名字叫作 DefaultConnectionStringResolver
,它的鏈接字符串都是從 AbpDbConnectionOptions
當中獲取的,而這個 Option 最終是從 IConfiguration
映射過來的,通常來講就是你 appsetting.json
文件當中的鏈接字符串配置。
多租戶解析器 的實現叫作 MultiTenantConnectionStringResolver
,它的內部核心邏輯就是得到到當前的租戶,並查詢租戶所對應的鏈接字符串,這樣就能夠實現每一個租戶都擁有不一樣的數據庫實例。
回到最開始的地方,方法 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; } }
關於倉儲的注入,其實在以前的文章就有講過,這裏我就大概說一下狀況。
在上述代碼當中,調用了 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
類型做爲默認倉儲的實現。
在 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; } }
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()); }
能夠看到有兩處使用,分別是 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 有使用。
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; } // ... }
在講解事件總線與 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); } }
點擊我 跳轉到文章總目錄。