簡體   English   中英

EF Core 中實現的存儲庫:使用整個 DbContext 還是僅使用 DbSet?

[英]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,因為它太慢了。”

使用我可以提供的存儲庫模式的最佳理由歸結為兩件事:

  1. 使單元測試更容易。 (存儲庫通常比 DbContext/DbSets 更容易模擬)

  2. 集中核心過濾規則。 例如在軟刪除系統中,集中 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因為這仍然可以通過SelectProjectTo進行投影,以及我的消費者會知道它需要的急切加載場景,或者如果我只想檢查一下,只需執行.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM