[英]Repository implemented in EF Core: use whole DbContext or only DbSet?
鑒於 EF Core 已經實現了存儲庫模式 ( DbSet
) 和工作單元 ( DbContext
),我可以像這樣在我的存儲庫中直接使用DbSet
嗎?
public class MyEfRepository : IMyRepository
{
private readonly DbSet<MyModel> _myModels;
public MyEfRepository(MyDbContext ctx)
{
_myModels = ctx.MyModels;
}
public MyModel FindById(Guid id)
{
return _myModels.Where(x => x.Id == id).SingleOrDefault();
}
}
並以下列方式實施工作單元?
public class MyEfUnitOfWork : IMyUnitOfWork
{
private readonly MyDbContext _ctx;
public IMyRepository MyModels { get; }
public MyEfUnitOfWork(MyDbContext ctx, MyEfRepository repo)
{
_ctx = ctx;
MyModels = repo;
}
void Commit() => _ctx.SaveChanges();
}
我想知道,因為我正在閱讀的每個指南都建議將整個DbContext
注入存儲庫,並在FindById
等方法中通過它訪問DbSet
。 由於工作單元將提交所有更改,我的方法不是更有意義嗎? 還是我不知道什么?
典型的方法是注入 DbContext 而不是 DbSets,因為配置 DI 容器以提供 DbContext 比從作用域 DbContext 實例中的每個 DbSet 更簡單。
關於評論“額外的層是為了更容易維護代碼,以防我想從 EF Core 更改為其他一些實現。” 出於這個原因,我強烈建議不要采用存儲庫。 這種立場的理由是,通過嘗試抽象出 EF,您會嚴重限制 EF 可以為您的應用程序提供的功能和性能,或者您會引入大量復雜性和開銷來嘗試和維護其中的一些功能。
舉一個經典的例子:
public IEnumerable<Customer> GetAllCustomers()
{
return _context.Customers.ToList();
}
像這樣的方法會將數據庫中的所有客戶加載到 memory 中。 如果您想過濾記錄、排序或分頁結果,會發生什么? 如果你只想要一個計數會發生什么? 只需要 ID 和幾列的情況呢?
甚至更簡單的例子:
public Customer GetCustomerById(int customerId)
{
return _context.Customers.Single(x => x.CustomerId == customerId);
}
這似乎足夠安全,但相關數據呢? 如果客戶有我想要檢索的地址、訂單等,我們要么依賴延遲加載,要么啟動額外的查詢,它如何知道通過預先加載相關數據是否會節省時間,以及哪些相關數據? (與渴望加載所有內容相比)
很快代碼就開始包含參數和表達式等,以嘗試促進快速加載、分頁、排序等。添加方法來執行諸如獲取計數或將結果投影到特定 DTO/ViewModel 而不是返回實體的操作,否則使用上述方法將導致嚴重的性能問題。 它變成了一個自我實現的預言。 “我將 EF 抽象出來,以防將來需要替換它……我需要替換 EF,因為它太慢了。”
使用我可以提供的存儲庫模式的最佳理由歸結為兩件事:
使單元測試更容易。 (存儲庫通常比 DbContext/DbSets 更容易模擬)
集中核心過濾規則。 例如在軟刪除系統中,集中 IsActive 標志過濾。 這也可以集中諸如授權檢查之類的事情。
我個人使用的第三個原因是存儲庫充當了一個很好的工廠 class 用於驗證輸入並返回“足夠完整”的實體。 例如,在保存實體之前通常需要必填字段和關系,以及可選字段。 存儲庫中的工廠方法可確保提供所有必需的詳細信息,並有權訪問 DbContext 以驗證/加載相關引用。
我使用的存儲庫模式使用IQueryable
為單元測試提供基本抽象,同時保持存儲庫本身非常簡單、輕量和靈活。
public IQueryable<Customer> GetAllCustomers()
{
return _context.Customers.AsQueryable();
}
public IQueryable<Customer> GetCustomerById(int customerId)
{
return _context.Customers.Where(x => x.CustomerId == customerId);
}
即使對於隱含的單數 select (ById) 我返回IQueryable
因為這仍然可以通過Select
或ProjectTo
進行投影,以及我的消費者會知道它需要的急切加載場景,或者如果我只想檢查一下,只需執行.Any()
如果一個項目存在。
例如,我在一個地方的消費代碼可以使用:
var customer = Repository.GetCustomerById(customerId)
.Include(x => x.Orders)
// ....
.Single();
...該代碼可能會更新有關客戶的一些數據及其相關訂單。
而另一個消費語句可能會使用:
var customer = Repository.GetCustomerById(customerId)
.ProjectTo<CustomerSummaryViewModel>(config)
.Single();
...此代碼僅對投影客戶和相關數據的摘要視圖 model 感興趣。
如果我們想綁定 IsActive 檢查,或確保返回的數據查看當前登錄的用戶和任何數據限制等。存儲庫是綁定這些非常通用的過濾器的好地方。如果您不打算合並單元測試或這些類型的統一檢查,那么存儲庫並沒有真正給您帶來任何好處。
請注意,這種抽象並沒有隱藏我們依賴實體框架的事實,也不應該嘗試這樣做。 試圖隱藏 EF 意味着要么我們放棄 EF 可以為我們提供的與域一起工作的大部分功能和靈活性,要么我們需要探索高度復雜的代碼來嘗試適應它可以提供的東西。盒子。 即使是巧妙的解決方案,例如將表達式作為參數傳遞以嘗試處理排序、急切加載或投影以避免暴露/污染具有 EF 特定或域特定規則/知識的代碼,最終也是有缺陷的,因為它們仍然必須符合 EF 特定規則。 例如,提供給 EF 的那些表達式不得包含方法調用或引用未映射的屬性等。它們必須了解域並且仍然符合 EF 可以理解的內容。 唯一真正的解決方法是實現你自己的表達式解析器,這真的非常不值得。 :)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.