日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

仓储模式到底是不是反模式?

發布時間:2023/12/4 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 仓储模式到底是不是反模式? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【導讀】倉儲模式我們已耳熟能詳,但當我們將其進行應用時,真的是那么得心應手嗎?確定是解放了生產力嗎?這到底是怎樣的一個存在,確定不是反模式?

一篇詳文我們探討倉儲模式,這里僅我個人的思考,若有更深刻的理解,請在留言中給出

倉儲反模式

5年前我在Web APi中使用EntityFramework中寫了一個倉儲模式,并將其放在我個人github上,此種模式也完全是參考所流行的網傳模式,現如今在我看來那是極其錯誤的倉儲模式形式,當時在EntityFramework中有IDbSet接口,然后我們又定義一個IDbContext接口等等,大同小異,接下來我們看看在.NET Core中大多是如何使用的呢?

?????定義通用IRepository接口

public?interface?IRepository<TEntity>?where?TEntity?:?class {///?<summary>///?通過id獲得實體///?</summary>///?<param?name="id"></param>///?<returns></returns>TEntity?GetById(object?id);//其他諸如修改、刪除、查詢接口 }

當然還有泛型類可能需要基礎子基礎類等等,這里我們一并忽略

?????定義EntityRepository實現IRepository接口

public?abstract?class?EntityRepository<TEntity>?:?IRepository<TEntity>?where?TEntity?:?class {private?readonly?DbContext?_context;public?EntityRepository(DbContext?context){_context?=?context;}///?<summary>///?通過id獲取實體///?</summary>///?<param?name="id"></param>///?<returns></returns>public?TEntity?GetById(object?id){return?_context.Set<TEntity>().Find(id);} }

?????定義業務倉儲接口IUserRepository接口

public?interface?IUserRepository?:?IRepository<User> {///?<summary>///?其他非通用接口///?</summary>///?<returns></returns>List<User>?Other(); }

?????定義業務倉儲接口具體實現UserRepository

public?class?UserRepository?:?EntityRepository<User>,?IUserRepository {public?List<User>?Other(){throw?new?NotImplementedException();} }

我們定義基礎通用接口和實現,然后每一個業務都定義一個倉儲接口和實現,最后將其進行注入,如下:

??services.AddDbContext<EFCoreDbContext>(options?=>{options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");});services.AddScoped(typeof(IRepository<>),?typeof(EntityRepository<>));services.AddScoped<IUserRepository,?UserRepository>();services.AddScoped<IUserService,?UserService>());

有一部分童鞋在項目中可能就是使用如上方式,每一個具體倉儲實現我們將其看成傳統的數據訪問層,緊接著我們還定義一套業務層即服務層,如此第一眼看來和傳統三層架構無任何區別,只是分層名稱有所不同而已

每一個具體倉儲接口都繼承基礎倉儲接口,然后每個具體倉儲實現繼承基礎倉儲實現,對于服務層同理,反觀上述一系列操作本質,其實我們回到了原點,那還不如直接通過上下文操作一步到位來的爽快

上述倉儲模式并沒有帶來任何益處,分層明確性從而加大了復雜性和重復性,根本沒有解放生產率,我們將專注力全部放在了定義多層接口和實現上而不是業務邏輯,如此使用,這就是倉儲模式的反模式實現

倉儲模式思考

所有脫離實際項目和業務的思考都是耍流氓,若只是小型項目,直接通過上下文操作未嘗不可,既然用到了倉儲模式說明是想從一定程度上解決項目中所遇到的痛點所在,要不然只是隨波逐流,終將是自我打臉

根據如下官方在微服務所使用倉儲鏈接,官方推崇倉儲模式,但在其鏈接中是直接在具體倉儲實現中所使用上下文進行操作,毫無以為這沒半點毛病

EntityFramework?Core基礎設施持久化層

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implementation-entity-framework-core

但我們想在上下文的基礎上進一步將基本增、刪、改、查詢進行封裝,那么我們如何封裝基礎倉儲而避免出現反模式呢?

我思倉儲模式

在進行改造之前,我們思考兩個潛在需要解決的重點問題

其一,每一個具體業務倉儲實現,定義倉儲接口是一定必要的嗎?我認為完全沒必要,有的童鞋就疑惑了,若我們有非封裝基礎通用接口,需額外定義,那怎么搞,我們可以基于基礎倉儲接口定義擴展方法

其二,若與其他倉儲進行互操作,此時基礎倉儲不滿足需求,那怎么搞,我們可以在基礎倉儲接口中定義暴露獲取上下文Set屬性

其三,若非常復雜的查詢,可通過底層連接實現或引入Dapper

首先,我們保持上述封裝基礎倉儲接口前提下添加暴露上下文Set屬性,如下:

??///?<summary>///?基礎通用接口///?</summary>///?<typeparam?name="TEntity"></typeparam>public?interface?IRepository<T>?where?T?:?class{IQueryable<T>?Queryable?{?get;?}T?GetById(object?id);}

上述我們將基礎倉儲接口具體實現類,將其定義為抽象,既然我們封裝了針對基礎倉儲接口的實現,外部只需調用即可,那么該類理論上就不應該被繼承,所以接下來我們將其修飾為密封類,如下:

public?sealed?class?EntityRepository<T>?:?IRepository<T>?where?T?:?class {private?readonly?DbContext?_context;public?EntityRepository(DbContext?context){_context?=?context;}public?T?GetById(object?id){return?_context.Set<T>().Find(id);} }

我們從容器中獲取上下文并進一步暴露上下文Set屬性

public?sealed?class?EntityRepository<T>?:?IRepository<T>?where?T?:?class {private?readonly?IServiceProvider?_serviceProvider;private?EFCoreDbContext?_context?=>?(EFCoreDbContext)_serviceProvider.GetService(typeof(EFCoreDbContext));private?DbSet<T>?Set?=>?_context.Set<T>();public?IQueryable<T>?Queryable?=>?Set;public?EntityRepository(IServiceProvider?serviceProvider){_serviceProvider?=?serviceProvider;}public?T?GetById(object?id){return?Set.Find(id);} }

若為基礎倉儲接口不滿足實現,則使用具體倉儲的擴展方法

public?static?class?UserRepository {public?static?List<User>?Other(this?IRepository<User>?repository){//?自定義其他實現} }

最后到了服務層,則是我們的業務層,我們只需要使用上述基礎倉儲接口或擴展方法即可

public?class?UserService {private?readonly?IRepository<User>?_repository;public?UserService(IRepository<User>??repository){_repository?=?repository;} }

最后在注入時,我們將省去注冊每一個具體倉儲實現,如下:

??services.AddDbContext<EFCoreDbContext>(options?=>{options.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;");});services.AddScoped(typeof(IRepository<>),?typeof(EntityRepository<>));services.AddScoped<UserService>();

以上只是針對第一種反模式的基本改造,對于UnitOfWork同理,其本質不過是管理操作事務,并需我們手動管理上下文釋放時機就好,這里就不再多講

我們還可以根據項目情況可進一步實現其對應規則,比如在是否需要在進行指定操作之前實現自定義擴展,比如再抽取一個上下文接口等等,ABP vNext中則是如此,ABP vNext對EF Core擴展是我看過最完美的實現方案,接下來我們來看看

ABP?vNext倉儲模式

其核心在Volo.Abp.EntityFrameworkCore包中,將其單獨剝離出來除了抽象通用封裝外,還有一個則是調用了EF Core底層APi,一旦EF Core版本變動,此包也需同步更新

ABP vNext針對EF Core做了擴展,通過查看整體實現,主要通過擴展中特性實現指定屬性更新,EF Core中當模型被跟蹤時,直接提交則更新變化屬性,若未跟蹤,我們直接Update但想要更新指定屬性,這種方式不可行,在ABP vNext則得到了良好的解決

在其EF Core包中的AbpDbContext上下文中,針對屬性跟蹤更改做了良好的實現,如下:

? protected?virtual?void?ChangeTracker_Tracked(object?sender,?EntityTrackedEventArgs?e){FillExtraPropertiesForTrackedEntities(e);}protected?virtual?void?FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs?e){var?entityType?=?e.Entry.Metadata.ClrType;if?(entityType?==?null){return;}if?(!(e.Entry.Entity?is?IHasExtraProperties?entity)){return;}.....}

除此之外的第二大亮點則是對UnitOfWork(工作單元)的完美方案,將其封裝在Volo.Abp.Uow包中,通過UnitOfWorkManager管理UnitOfWork,其事務提交不簡單是像如下形式

private?IDbContextTransaction?_transaction;? public?void?BeginTransaction() {?_transaction?=?Database.BeginTransaction(); }public?void?Commit() {try{SaveChanges();_transaction.Commit();}finally{_transaction.Dispose();}???????? }public?void?Rollback() {?_transaction.Rollback();_transaction.Dispose(); }

額外的還實現了基于環境流動的事務(AmbientUnitOfWork),反正ABP vNext在EF Core這塊擴展實現令人嘆服,我也在持續學習中,其他就不多講了,博客園中講解原理的文章比比皆是

好了,本文到此結束,倒沒什么可總結的,在文中已有概括,我們下次再會

總結

以上是生活随笔為你收集整理的仓储模式到底是不是反模式?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。