Abp 框架在其內部實現了倉儲模式,而且支持 EF Core 與 Dapper 來進行數據庫鏈接與管理,你能夠很方便地經過注入通用倉儲來操做你的數據,而不須要你本身來爲每個實體定義單獨的倉儲的實現,通用倉儲包含了經常使用的 CRUD 接口和一些經常使用方法。html
例如:數據庫
public class TestAppService : ITransientDependency { private readonly IRepository<TestTable> _rep; // 注入通用倉儲 public TestAppService(IRepository<TestTable> rep) { _rep = rep; } public void TestMethod() { // 插入一條新數據 _rep.Insert(new TestTable{ Name = "TestName" }); } }
在 Abp 內部,倉儲的基本定義存放在 Abp 項目的 Domain/Repositories 內部,包括如下幾個文件:app
文件名稱 | 做用描述 |
---|---|
AbpRepositoryBase.cs | 倉儲基類 |
AutoRepositoryTypesAttribute.cs | 自動構建倉儲,用於實體標記 |
IRepository.cs | 倉儲基本接口定義 |
IRepositoryOfTEntity.cs | 倉儲接口定義,默認主鍵爲 int 類型 |
IRepositoryOfTEntityAndTPrimaryKey.cs | 倉儲接口定義,主鍵與實體類型由用戶定義 |
ISupportsExplicitLoading.cs | 顯式加載 |
RepositoryExtensions.cs | 倉儲相關的擴展方法 |
綜上所述,倉儲的基礎定義是由 IRepository
決定的,這個接口沒什麼其餘用處,就如同 ITransientDependency
接口與 ISingletonDependency
同樣,只是作一個標識做用。框架
真正定義了倉儲接口的是在 IRepositoryOfTEntityAndTPrimaryKey<TEntity, TPrimaryKey>
內部,他的接口定義以下:async
public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : class, IEntity<TPrimaryKey> { // CRUD 方法 }
能夠看到,他有兩個泛型參數,第一個是實體類型,第二個是實體的主鍵類型,而且約束了 TEntity
必須實現了 IEntity<TPrimaryKey>
接口,這是由於在倉儲接口內部的一些方法須要獲得實體的主鍵纔可以操做,好比修改與查詢方法。ide
在 Abp 內部還有另一個倉儲的定義,叫作 IRepository<TEntity>
,這個接口就是默認你的主鍵類型爲 int
類型,通常不多使用 IRepository<TEntity, TPrimaryKey>
更多的仍是用的 IRepository<TEntity>
。函數
在 Abp 庫裏面,有一個默認的抽象基類實現了倉儲接口,這個基類內部主要注入了 IUnitOfWorkManager
用來控制事務,還有 IIocResolver
用來解析 Ioc 容器內部註冊的組件。ui
自己在這個抽象倉儲類裏面沒有什麼實質性的東西,它只是以前 IRepository<TEntity>
的簡單實現,在 EfCoreRepositoryBase
類當中則纔是具體調用 EF Core API 的實現。3d
public class EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey> : AbpRepositoryBase<TEntity, TPrimaryKey>, ISupportsExplicitLoading<TEntity, TPrimaryKey>, IRepositoryWithDbContext where TEntity : class, IEntity<TPrimaryKey> where TDbContext : DbContext { /// <summary> /// 得到數據庫上下文 /// </summary> public virtual TDbContext Context => _dbContextProvider.GetDbContext(MultiTenancySide); /// <summary> /// 具體的實體表 /// </summary> public virtual DbSet<TEntity> Table => Context.Set<TEntity>(); // 數據庫事務 public virtual DbTransaction Transaction { get { return (DbTransaction) TransactionProvider?.GetActiveTransaction(new ActiveTransactionProviderArgs { {"ContextType", typeof(TDbContext) }, {"MultiTenancySide", MultiTenancySide } }); } } // 數據庫鏈接 public virtual DbConnection Connection { get { var connection = Context.Database.GetDbConnection(); if (connection.State != ConnectionState.Open) { connection.Open(); } return connection; } } // 事務提供器,用於獲取已經激活的事務 public IActiveTransactionProvider TransactionProvider { private get; set; } private readonly IDbContextProvider<TDbContext> _dbContextProvider; /// <summary> /// 構造函數 /// </summary> /// <param name="dbContextProvider"></param> public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider) { _dbContextProvider = dbContextProvider; } }
其實從上方就能夠看出來,Abp 對於每個倉儲都會從新打開一個數據庫連接,在 EfCoreRepositoryBase
裏面的 CRUD 方法實際上都是針對 DbContext
來進行的操做。code
舉個例子:
// 插入數據 public override TEntity Insert(TEntity entity) { return Table.Add(entity).Entity; } // 更新數據 public override TEntity Update(TEntity entity) { AttachIfNot(entity); Context.Entry(entity).State = EntityState.Modified; return entity; } // 附加實體狀態 protected virtual void AttachIfNot(TEntity entity) { var entry = Context.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity); if (entry != null) { return; } Table.Attach(entity); }
這裏須要注意的是 Update()
方法,以前遇到過一個問題,假如我傳入了一個實體,它的 ID 是不存在的,那麼我將這個實體傳入 Update()
方法以後執行 SaveChanges()
的時候,會拋出 DbUpdateConcurrencyException
異常。
正確的操做是先使用實體的 ID 去查詢數據庫是否存在該條記錄,存在再執行 Update()
操做。
這裏 AttachIfNot 做用是將實體附加到追蹤上下文當中,若是你以前是經過
Get()
方法獲取實體以後更改了某個實體,那麼在調用Context.ChangeTracker.Entries()
方法的時候會獲取到已經發生變更的身體對象集合。
倉儲的注入操做發生在 AbpEntityFrameworkCoreModule
模塊執行 Initialize()
方法的時候,在 Initialize()
方法內部調用了 RegisterGenericRepositoriesAndMatchDbContexes()
方法,其定義以下:
private void RegisterGenericRepositoriesAndMatchDbContexes() { // 查找全部數據庫上下文 var dbContextTypes = _typeFinder.Find(type => { var typeInfo = type.GetTypeInfo(); return typeInfo.IsPublic && !typeInfo.IsAbstract && typeInfo.IsClass && typeof(AbpDbContext).IsAssignableFrom(type); }); if (dbContextTypes.IsNullOrEmpty()) { Logger.Warn("No class found derived from AbpDbContext."); return; } using (IScopedIocResolver scope = IocManager.CreateScope()) { // 遍歷數據庫上下文 foreach (var dbContextType in dbContextTypes) { Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName); // 爲數據庫上下文每一個實體註冊倉儲 scope.Resolve<IEfGenericRepositoryRegistrar>().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default); // 爲自定義的 DbContext 註冊倉儲 IocManager.IocContainer.Register( Component.For<ISecondaryOrmRegistrar>() .Named(Guid.NewGuid().ToString("N")) .Instance(new EfCoreBasedSecondaryOrmRegistrar(dbContextType, scope.Resolve<IDbContextEntityFinder>())) .LifestyleTransient() ); } scope.Resolve<IDbContextTypeMatcher>().Populate(dbContextTypes); } }
方法很簡單,註釋已經說的很清楚了,就是遍歷實體,經過 EfGenericRepositoryRegistrar
與 EfCoreBasedSecondaryOrmRegistrar
來註冊倉儲。
來看一下具體的註冊操做:
private void RegisterForDbContext( Type dbContextType, IIocManager iocManager, Type repositoryInterface, Type repositoryInterfaceWithPrimaryKey, Type repositoryImplementation, Type repositoryImplementationWithPrimaryKey) { foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType)) { // 獲取主鍵類型 var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType); if (primaryKeyType == typeof(int)) { // 創建倉儲的封閉類型 var genericRepositoryType = repositoryInterface.MakeGenericType(entityTypeInfo.EntityType); if (!iocManager.IsRegistered(genericRepositoryType)) { // 構建具體的倉儲實現類型 var implType = repositoryImplementation.GetGenericArguments().Length == 1 ? repositoryImplementation.MakeGenericType(entityTypeInfo.EntityType) : repositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType); // 注入 iocManager.IocContainer.Register( Component .For(genericRepositoryType) .ImplementedBy(implType) .Named(Guid.NewGuid().ToString("N")) .LifestyleTransient() ); } } // 若是主鍵類型爲 int 以外的類型 var genericRepositoryTypeWithPrimaryKey = repositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType,primaryKeyType); if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey)) { // 操做跟上面同樣 var implType = repositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2 ? repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType) : repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType); iocManager.IocContainer.Register( Component .For(genericRepositoryTypeWithPrimaryKey) .ImplementedBy(implType) .Named(Guid.NewGuid().ToString("N")) .LifestyleTransient() ); } } }
這裏 RegisterForDbContext()
方法傳入的這些開放類型實際上是經過 EfCoreAutoRepositoryTypes.Default
屬性指定,其定義:
public static class EfCoreAutoRepositoryTypes { public static AutoRepositoryTypesAttribute Default { get; } static EfCoreAutoRepositoryTypes() { Default = new AutoRepositoryTypesAttribute( typeof(IRepository<>), typeof(IRepository<,>), typeof(EfCoreRepositoryBase<,>), typeof(EfCoreRepositoryBase<,,>) ); } }
在以前的文章裏面說過,Abp 自己只實現了一個抽象工做單元基類 UnitOfWorkBase
,而具體的事務處理是存放在具體的持久化模塊裏面進行實現的,在 EF Core 這裏則是經過 EfCoreUnitOfWork
實現的。
首先看一下 EfCoreUnitOfWork
注入了哪些東西:
public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency { protected IDictionary<string, DbContext> ActiveDbContexts { get; } protected IIocResolver IocResolver { get; } private readonly IDbContextResolver _dbContextResolver; private readonly IDbContextTypeMatcher _dbContextTypeMatcher; private readonly IEfCoreTransactionStrategy _transactionStrategy; /// <summary> /// 建立一個新的 EF UOW 對象 /// </summary> public EfCoreUnitOfWork( IIocResolver iocResolver, IConnectionStringResolver connectionStringResolver, IUnitOfWorkFilterExecuter filterExecuter, IDbContextResolver dbContextResolver, IUnitOfWorkDefaultOptions defaultOptions, IDbContextTypeMatcher dbContextTypeMatcher, IEfCoreTransactionStrategy transactionStrategy) : base( connectionStringResolver, defaultOptions, filterExecuter) { IocResolver = iocResolver; _dbContextResolver = dbContextResolver; _dbContextTypeMatcher = dbContextTypeMatcher; _transactionStrategy = transactionStrategy; ActiveDbContexts = new Dictionary<string, DbContext>(); } }
emmm,他注入的基本上都是與 EfCore 有關的東西。
第一個字典是存放處在激活狀態的 DbContext
集合,第二個是 IIocResolver
用於解析組件所須要的解析器,第三個是數據庫上下文的解析器用於建立 DbContext
的,第四個是用於查找 DbContext
的 Matcher,最後一個就是用於 EF Core 事物處理的東東。
根據 UnitOfWork
的調用順序,首先看查看 BeginUow()
方法:
if (Options.IsTransactional == true) { _transactionStrategy.InitOptions(Options); }
沒什麼特殊操做,就拿着 UOW 對象的 Options 去初始化事物策略。
以後按照 UOW 的調用順序(PS:若是看的一頭霧水能夠去看一下以前文章針對 UOW 的講解),會調用基類的 CompleteAsync()
方法,在其內部則是會調用 EF Core UOW 實現的 CompleteUowAsync()
方法,其定義以下:
protected override async Task CompleteUowAsync() { // 保存全部 DbContext 的更改 await SaveChangesAsync(); // 提交事務 CommitTransaction(); } public override async Task SaveChangesAsync() { foreach (var dbContext in GetAllActiveDbContexts()) { await SaveChangesInDbContextAsync(dbContext); } } private void CommitTransaction() { if (Options.IsTransactional == true) { _transactionStrategy.Commit(); } }
內部很簡單,兩句話,第一句話遍歷全部激活的 DbContext
,而後調用其 SaveChanges()
提交更改到數據庫當中。
以後呢,第二句話就是使用 DbContext
的 dbContext.Database.CommitTransaction();
方法來提交一個事務咯。
public void Commit() { foreach (var activeTransaction in ActiveTransactions.Values) { activeTransaction.DbContextTransaction.Commit(); foreach (var dbContext in activeTransaction.AttendedDbContexts) { if (dbContext.HasRelationalTransactionManager()) { continue; //Relational databases use the shared transaction } dbContext.Database.CommitTransaction(); } } }
這個玩意兒的定義以下:
public interface IDbContextProvider<out TDbContext> where TDbContext : DbContext { TDbContext GetDbContext(); TDbContext GetDbContext(MultiTenancySides? multiTenancySide ); }
很簡單的做用,獲取指定類型的數據庫上下文,他的標準實現是 UnitOfWorkDbContextProvider<TDbContext>
,它依賴於 UOW ,使用 UOW 的 GetDbContext<TDbContext>()
方法來取得數據庫上下文。
整個關係以下:
在 Abp 內部針對多數據庫支持是經過覆寫 IConnectionStringResolver
來實現的,這個操做在以前的文章裏面已經講過,這裏僅講解它如何在 Abp 內部實現解析的。
IConnectionStringResolver
是在 EF 的 Uow 纔會用到,也就是建立 DbContext
的時候:
public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null) where TDbContext : DbContext { var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext)); var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide); connectionStringResolveArgs["DbContextType"] = typeof(TDbContext); connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType; // 這裏調用了 Resolver var connectionString = ResolveConnectionString(connectionStringResolveArgs); // 建立 DbContext dbContext = _transactionStrategy.CreateDbContext<TDbContext>(connectionString, _dbContextResolver); return (TDbContext)dbContext; } // 傳入了 ConnectionStringResolveArgs 裏面包含了實體類型信息哦 protected virtual string ResolveConnectionString(ConnectionStringResolveArgs args) { return ConnectionStringResolver.GetNameOrConnectionString(args); }
他這裏的默認實現叫作 DefaultConnectionStringResolver
,就是從 IAbpStartupConfiguration
裏面拿去用戶在啓動模塊配置的 DefaultNameOrConnectionString
字段做爲本身的默認數據庫鏈接字符串。
在以前的 文章 的思路也是經過傳入的 ConnectionStringResolveArgs
參數來判斷傳入的 Type,從而來根據不一樣的 DbContext
返回不一樣的鏈接串。