我有兩個獨立的資料庫。資料庫 A 較小,包含 6 個表和一些存盤程序,資料庫 B 較大,包含 52 個表、159 個視圖和一些存盤程序。我的任務是創建一個對這兩個資料庫都執行操作的應用程式。已經撰寫了訪問這些資料庫的代碼,但它不可測驗,我想從頭開始創建 DAL 代碼。所以我創建了一個代碼優先的物體框架連接,讓它為我創建所有的 POCO 以及 Context 類。現在我有 3 個 DatabaseContext 類。1 用于資料庫 A,它只是 6 個單獨的表。1 用于資料庫 B 中的表,1 用于資料庫 B 中的視圖。我將資料庫 B 的 Context 類拆分為 2 個單獨的背景關系類,因為表和視圖用于 2 個不同的場景。
我在一個解決方案中創建了 2 個專案。DomainModel 和 DataAccessLayer。DomainModel 專案包含物體框架代碼首先為我生成的所有 POCO。DataAccessLayer 是 DatabaseContexts 所在的位置,我想在其中創建其他專案可以用來訪問資料庫的代碼。我的目標是制作可重用和可測驗的資料訪問層代碼。
問題是:我在 DataAccessLayer 專案中創建什么以使 DAL 在其他希望能夠以抽象方式與資料庫對話的專案中可重用,以及構建它的最佳方式是什么?
我所做的:在我在互聯網上的搜索中,我看到人們推薦存盤庫模式(有時使用 UnitOfWork),這似乎是要走的路,所以我創建了一個名為 IGenericRepository 的介面和一個名為 GenericEntityFrameworkRepository 的實作。該介面有GetByID、Insert、Delete、Update等方法。該實作將 DbContext 作為建構式中的引數并實作所有方法。下一步可能是為不同的表和視圖創建特定的實作,但這將是一件非常乏味的事情,因為有這么多的表和視圖。我還讀過物體框架是 DAL。然后創建另一個包含業務邏輯并回傳 IEnumerables 的專案可能就足夠了。但這似乎很難測驗。
我想要實作的是一個結構良好且可測驗的基礎來訪問我們的資料庫,當其他專案開始需要資料庫的其他功能時,可以對其進行徹底測驗和擴展。
專案的檔案夾結構如下圖所示:

uj5u.com熱心網友回復:
設計和架構都是關于權衡的。當您想考慮抽象您的資料訪問以供重用時要小心。在應用程式中,這會導致解決方案過于復雜且性能不佳,而這可能是一個非常簡單和快速的解決方案。
例如,如果我想將業務領域層構建為一組服務,供許多不同的應用程式(為移動應用程式或第三方利益服務的 Web 應用程式和服務/API)使用,那么該領域層將成為一個邊界它定義了哪些資訊會進來,哪些資訊會出去。進出該抽象的所有內容都應該是反映我通過我提供的操作提供的域的 DTO。在該邊界內,應利用 EF 執行生成相關 DTO 所需的預測,或根據傳入的特定詳細資訊更新物體。(驗證后)權衡是每個消費者都需要使用這個通用的接入點,因此除非您將其內置,否則幾乎沒有回旋余地來自定義輸出的內容。
不過,我最常看到的是,在單個解決方案中,開發人員希望將系統抽象為不“知道”資料域是由 EF 提供的。應用程式繞過 POCO 物體或 DTO,它們使用 GetById、GetAll、Update、Upsert 等實作通用存盤庫之類的東西。這是一個非常非常糟糕的設計決策,因為這意味著您的資料域無法利用投影之類的東西,分頁、自定義過濾、排序、急切加載相關資料等。抽象 EF DbContext 無疑是啟用單元測驗的有效目標,但我強烈建議避免 Generic Repository 模式的性能缺陷。IMO 試圖僅僅為了隱藏它或使其可交換而抽象掉 EF,要么表現不佳和/或過于復雜。
以典型的 GetAll() 方法為例。
public IEnumerable<T> GetAll()
{
return _context.Set<T>().ToList();
}
如果您只需要最近 3 個月的訂單,會發生什么?
var orders = orderRepository.GetAll()
.Where(x => x.OrderDate >= startDate).ToList();
這里的問題是 GetAll() 將始終回傳所有行。如果您有 400 萬份訂單,您會意識到這是不可取的。
您可以創建一個擴展存盤庫的 OrderRepository 以實作像 GetOrdersAfter(DateTime) 這樣的方法,但是很快,通用存盤庫的意義何在?下一個選項是將類似于Expression<Func<TEntity>>Where 子句的內容傳遞給 GetAll() 方法。有很多這樣的例子。但是,這樣做會將 EF-isms泄漏到消費代碼中。Where 子句運算式必須符合 EF 規則。例如,傳遞order => order.SomeUnmappedProperty == someValue會導致查詢失敗,因為 EF 無法將其轉換為 SQL。即使您對此感到滿意, GetAll 方法也需要開始看起來像:
IEnumerable<TEntity> GetAll(Expression<Func<TEntity>> whereClause = null,
IEnumerable<OrderByClause> orderByClauses = null,
IEnumerable<string> includes = null,
int? pageNumber = null, int? pageSize = null )
或一些類似的怪物來處理過濾、排序、急切加載和分頁,這甚至不包括投影。為此,您最終會使用其他方法,例如:
IEnumerable<TEntity, TDTO> GetAll(Expression<Func<TEntity>> whereClause = null,
IEnumerable<OrderByClause> orderByClauses = null,
IEnumerable<string> includes = null,
int? pageNumber = null, int? pageSize = null )
它將呼叫另一個 GetAll 然后執行 Mapper.Map 以回傳希望在映射依賴項中注冊的所需 DTO/ViewModel。然后,您需要考慮async這些方法的與同步風格。(或強制所有呼叫同步或async)
總的來說,我的建議是避免通用存盤庫模式,不要僅僅為了抽象而抽象掉 EF,而是尋找方法來最大限度地利用它。開發人員在使用 EF 時遇到的大多數性能問題和奇怪行為都源于對它進行抽象的努力。他們非常擔心需要抽象它以便能夠在需要時替換它,以至于他們最終會遇到這些性能和復雜性問題,完全是由于抽象所施加的限制。(一個自我實作的預言)當你試圖過早地優化一個解決方案來解決你目前沒有并且可能永遠不會面對的問題時,“架構”可能是一個骯臟的詞。始終將 YAGNI 和 KISS 放在所有設計考慮的最前沿。
uj5u.com熱心網友回復:
我猜你在為給定的解決方案選擇正確的架構之間跌跌撞撞。必須創建一個軟體以滿足業務需求。所以根據業務,領域的復雜性,我們必須選擇寫架構。
微軟在詳細的 PDF 中對此進行了詳細闡述,您可以在此處找到它 https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/
您希望在資料和 BL 之間進行某種程度的抽象,那么“N 層”架構不是最佳解決方案。即使我沒有告訴 N 層完全過時,但對于這個問題有更合適的解決方案。
如果您使用 EF 或 EF 核心,則無需實作存盤庫 cus EF 本身包含 UOW 和存盤庫。
清潔架構在層之間引入了更高級別的抽象。它抽象出您的資料層,并使其可在不同的專案或不同的解決方案中重用。清潔架構是一個很大的話題,我無法在此詳細描述。但我從他那里學到了這門課。
杰森·泰勒——干凈的建筑
https://www.youtube.com/watch?v=dK4Yb6-LxAk
https://github.com/jasontaylordev/CleanArchitecture
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/494615.html
標籤:C# 实体框架 单元测试 ef-code-first 封装
