首頁 > 後端開發 > C++ > 使用存儲庫甚至 ORM 進行程式碼中的資料訪問

使用存儲庫甚至 ORM 進行程式碼中的資料訪問

Susan Sarandon
發布: 2024-12-25 11:21:10
原創
745 人瀏覽過

Data access in code, using repositories, even with ORMs

簡介

在 .NET 世界中,存取資料庫最常用的方法之一是使用實體框架 (EF),這是一種與語言語法緊密整合的物件關係映射器 (ORM)。使用 .NET 語言原生的語言整合查詢 (LINQ),讓資料存取感覺就像使用普通的 .NET 集合一樣,而無需了解太多 SQL 知識。這有其優點和缺點,我將盡量不在這裡咆哮。但它始終造成的問題之一是軟體專案結構、抽象層級和最終單元測試的混亂。

這篇文章將嘗試解釋為什麼儲存庫抽象總是有用的。請注意,許多人使用儲存庫作為抽象資料存取的術語,雖然也有與類似事物相關的儲存庫軟體模式,但它不是同一件事。在這裡,我將把存儲庫稱為一系列抽象資料存取的實現細節的接口,並完全忽略設計模式。

歷史

如果您意識到這一點,請隨意跳過這一點,但我必須先解決我們如何開始想到儲存庫的想法。

在史前時期,程式碼只是按原樣編寫,沒有結構,一切都在做你想讓它做或至少希望它做的事情。沒有自動化測試,只有手動駭客和測試,直到它起作用。每個應用程式都是用現有的任何東西編寫的,對硬體要求的關注比程式碼結構、重用或可讀性更重要。這就是恐龍被殺死的原因!真實的事實。

慢慢地,模式開始出現。特別是對於業務應用程序,業務代碼、資料持久性和使用者介面之間存在明顯的分離。這些被稱為層,很快就被分成不同的項目,不僅因為它們涵蓋了不同的關注點,而且因為建造它們所需的技能特別不同。 UI 設計與程式碼邏輯工作非常不同,與 SQL 或任何用於持久性資料的語言或系統也非常不同。

因此,業務和資料層的互動是透過抽象化成介面和模型來完成的。作為商務艙,您不會要求表中的條目列表,您將需要複雜物件的篩選列表。資料層有責任存取持久保存的內容並將其對應到業務可以理解的內容。這些抽象開始被稱為儲存庫。

在資料存取的較低層,CRUD 等模式很快就佔據了主導地位:您定義了表等結構化持久性容器,並且可以建立、讀取、更新或刪除記錄。在程式碼中,這種邏輯將被抽象化為集合,例如列表、字典或陣列。因此,目前還有一種觀點認為儲存庫的行為應該像集合一樣,甚至可能足夠通用,除了實際的建立、讀取、更新和刪除之外沒有其他方法。

但是,我強烈不同意。作為業務資料存取的抽象,它們應該盡可能遠離資料存取模式,而不是根據業務需求進行建模。這是實體框架(尤其是許多其他 ORM)的思維方式開始與儲存庫的原始想法發生衝突的地方,最終呼籲永遠不要將儲存庫與 EF 一起使用,稱其為反模式。

更多層數

模型之間的父子關係會產生很多混亂。就像一個部門實體,裡面有人員。部門儲存庫是否應該傳回包含人員的模型?也許不是。那麼,我們如何將儲存庫分成部門(沒有人員)和人員,然後有一個單獨的抽象來對應到業務模型?

當我們將業務層分成子層時,混亂實際上會增加。例如,大多數人所說的業務服務是僅將特定業務邏輯應用於特定類型的業務模型的抽象。假設您的應用程式與人一起工作,因此您有一個名為 Person 的模型。處理人員的類別將是 PeopleService,它將透過 PeopleRepository 從持久層取得業務模型,但也可以執行其他操作,包括資料模型和業務模型之間的對應或僅與人員相關的特定工作,例如計算工資。然而,大多數業務邏輯使用多種類型的模型,因此服務最終成為儲存庫上的映射包裝器,幾乎沒有額外的責任。

現在假設您正在使用 EF 存取資料。您必須宣告一個 DbContext 類,其中包含對應到 SQL 表的實體集合。您可以使用 LINQ 來迭代、過濾和映射它們,這些資料會在後台有效地轉換為 SQL 命令,並為您提供所需的內容,以及分層的父子結構。此轉換還負責內部業務資料類型的映射,例如特定的枚舉或奇怪的資料結構。那麼為什麼你甚至需要儲存庫,甚至可能需要服務?

我相信,雖然更多的抽象層似乎是毫無意義的開銷,但它們增加了人們對專案的理解,並提高了變革的速度和品質。顯然,這是一種平衡,我見過一些系統架構明顯要求所有軟體設計模式都在任何地方使用。抽像只有在提高程式碼可讀性和關注點分離時才有用。

原因

EF 變得麻煩的環境之一是單元測試。 DbContext 是一個複雜的系統,具有大量依賴項,必須花費大量的精力手動模擬。因此,微軟提出了一個想法:記憶體資料庫提供者。因此,為了測試任何內容,您只需使用記憶體資料庫即可完成。

請注意,在 Microsoft 頁面上,此測試方法現在標記為「不建議」。另請注意,即使在這些範例中,EF 也是由儲存庫抽象化的。

雖然記憶體資料庫測試有效,但它們增加了幾個不容易解決的問題:

  • 設定記憶體中的 DbContext 需要現有實體的所有相依性
  • 為每個測試設定和啟動記憶體資料庫都很慢
  • 為了獲得有效的資料庫輸出,您需要設定的內容遠多於您想要原子測試的內容

因此,最終發生的情況是,人們在「幫助程式」方法中設定資料庫中的所有內容,然後創建從這種難以理解且複雜的方法開始的測試,以測試甚至最小的功能。如果沒有此設置,任何包含 EF 程式碼的程式碼都將無法測試。

因此使用儲存庫的原因之一是將測試抽象移至 DbContext 之上。現在您根本不需要資料庫,只需要一個儲存庫模擬。然後使用真實資料庫在整合測試中測試您的儲存庫本身。記憶體資料庫非常接近真實資料庫,但也略有不同。

另一個原因(我承認我在現實生活中很少看到它具有實際價值)是您可能想要改變存取資料的方式。也許你想換成NoSql,或是一些記憶體分散式快取系統。或者,更有可能的是,您從一個資料庫結構開始,也許是一個整體資料庫,現在您想將其重構為具有不同表結構的多個資料庫。讓我立即告訴您,如果沒有儲存庫,這是不可能的。

特定於實體框架,您獲得的實體是映射到資料庫的活動記錄。您對一個實體進行更改並保存對另一個實體的更改,然後您突然也會在資料庫中更新第一個實體。或者也許您沒有,因為您沒有包含某些內容,或者上下文已更改。

EF 的支持者總是將實體追蹤宣傳為一件非常正面的事情。假設您從資料庫中取得一個實體,然後執行一些業務,然後更新該實體並儲存它。透過儲存庫,您將獲取數據,然後開展業務,然後再次獲取數據以執行一些更新。 EF 會將其保留在記憶體中,知道它在更改之前沒有更新,因此它永遠不會讀取它兩次。這是真的。他們描述了資料庫的記憶體緩存,它以某種方式感知資料庫更改並追蹤您從資料庫處理的所有內容,除非另有指示,否則將資料庫條目雙向映射到複雜的C# 實體並來回追蹤更改,同時深度嵌入在業務代碼中。就我個人而言,我認為這種過多的責任和缺乏關注點分離比使用它所獲得的任何性能更具破壞性。此外,透過一些初步的努力,所有這些功能仍然可以在儲存庫中抽象,甚至可以在儲存庫的另一層記憶體快取中抽象,同時保持業務、快取和資料存取之間的清晰邊界。

事實上,所有這一切的實際困難在於確定應該有單獨關注點的系統之間的邊界。例如,透過將過濾邏輯移至資料庫中的預存過程,可以獲得大量效能,但這會失去所用演算法的可測試性和可讀性。相反,使用 EF 或其他一些機制將所有邏輯轉移到程式碼中,效能較差,有時甚至不可行。或者資料實體成為業務實體的點在哪裡(請參閱上面的部門和人員範例)?

也許最好的策略是先定義這些邊界,然後決定採用哪些技術和設計。

我的結論

我相信應該始終使用服務和儲存庫抽象,即使儲存庫在底層使用實體框架或其他 ORM。這一切都歸結為關注點分離。我永遠不會認為實體框架是一個有用的軟體抽象,因為它帶有如此多的包袱,因此儲存庫非常適合在程式碼中對其進行抽象化。 EF 是一個有用的抽象,但用於資料庫訪問,而不是在軟體中。

我的軟體編寫理念是,從應用程式需求開始,為這些需求建立元件,並使用介面抽象化任何較低層級的功能。然後,您在下一個層級重複此過程,始終確保程式碼可讀,並且不需要了解使用的元件或目前層級所使用的元件。如果情況並非如此,那麼您就已經很好地分離了關注點。因此,由於沒有任何業務應用程式需要使用特定的資料庫或 ORM,因此資料層抽象化應該隱藏所有這些知識。

企業想要什麼?過濾後的人員清單? var people = service.GetFilteredListOfPeople(filter);不多不少。而服務方法只會 return mapPeople(repo.GetFilteredListOfPeople(mappedFilter));同樣,不多不少。回購如何取得人員、拯救人員或執行其他任何操作都不是服務關心的問題。您需要快取,然後實作一些實作 IPeopleRepository 並依賴 IPeopleRepository 的快取機制。您想要映射,請實作正確的 IMapper 介面。等等。

我希望我在這篇文章中沒有過於冗長。我特意保留了程式碼範例,因為這更多的是一個概念問題,而不是軟體問題。實體框架可能是我在這裡抱怨的大部分目標,但這適用於任何在小事上神奇地幫助你,但卻破壞了重要事情的系統。

希望有幫助!

以上是使用存儲庫甚至 ORM 進行程式碼中的資料訪問的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板