EF Core:Repository Pattern 要不要用?
爭議:DbContext 本身就是 Repository + Unit of Work
EF Core 的 DbContext 已經實作了:
- Repository Pattern → DbSet<T> 就是 Repository
- Unit of Work → SaveChanges() 就是 Commit
再包一層 Repository,不就是多此一舉?
支持用 Repository 的理由
// 1. 抽象化:Controller 不直接依賴 EF Core
public interface IUserRepository {
Task<User?> GetByIdAsync(int id);
Task<List<User>> GetActiveUsersAsync();
}
// 好處:換 ORM(Dapper)或換 DB(MongoDB)不影響上層
// 好處:單元測試可以 mock IUserRepository
反對用 Repository 的理由
// 1. 「洩漏抽象」:你最終還是會暴露 IQueryable
public interface IUserRepository {
IQueryable<User> GetAll(); // ← 這不就是 DbSet 嗎?
}
// 2. 重複包裝:每個 CRUD 都要寫一層
public async Task<User?> GetByIdAsync(int id) {
return await _db.Users.FindAsync(id); // 一行就結束,何必包?
}
// 3. 複雜查詢怎麼辦?
// 最終 Repository 會變成一個巨大的「查詢方法集」
GetUsersByAgeAndCityAndStatusOrderByNamePaginated(...)
實務建議
| 場景 | 建議 |
|---|---|
| 小型專案(< 20 表) | 直接用 DbContext,不需要 Repository |
| 中型專案 | 用 Service Layer + DbContext |
| 需要換 ORM 的可能性 | 用 Repository |
| 需要大量單元測試 | 用 Repository(方便 mock) |
| 複雜的查詢邏輯 | 用 Specification Pattern 或 Query Object |
// 推薦的中間路線:Service Layer(不需要 Repository)
public class UserService {
private readonly AppDbContext _db;
public UserService(AppDbContext db) => _db = db;
public async Task<User?> GetActiveUser(int id) {
return await _db.Users
.AsNoTracking()
.Where(u => u.Id == id && u.IsActive)
.FirstOrDefaultAsync();
}
}
// 直接在 Service 裡寫查詢邏輯,不多包一層
結論:沒有絕對的對錯。重要的是統一團隊風格,而不是追求「正確」的架構。