Repository Pattern已死?深入剖析.NET中的设计模式争议与最佳实践

作者:微信公众号:【架构师老卢】
3-15 15:43
10

一场激烈的技术辩论

在.NET生态中,鲜有话题能像**仓储模式(Repository Pattern)**这般引发激烈争论。有些开发者奉其为整洁架构的基石,另一些人则认为它是被Entity Framework Core(EF Core)淘汰的冗余设计。

那么,仓储模式真的消亡了吗?还是说它正在进化?本文将剖析正反双方观点,探讨其适用场景,并回答是否应在你的下一个.NET项目中采用它。


什么是仓储模式?

仓储模式是一种抽象数据访问的设计模式,旨在简化管理和测试。通过创建仓储类作为中间层,业务逻辑不再直接调用数据库。

典型的.NET仓储模式示例:

public interface IRepository<T> where T : class  
{  
    Task<T?> GetByIdAsync(int id);  
    Task<IEnumerable<T>> GetAllAsync();  
    Task AddAsync(T entity);  
    Task UpdateAsync(T entity);  
    Task DeleteAsync(T entity);  
}  

基于Entity Framework Core的实现:

public class Repository<T> : IRepository<T> where T : class  
{  
    protected readonly DbContext _context;  
    protected readonly DbSet<T> _dbSet;  

    public Repository(DbContext context)  
    {  
        _context = context;  
        _dbSet = context.Set<T>();  
    }  

    public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);  
    public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();  
    public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);  
    public async Task UpdateAsync(T entity) => _dbSet.Update(entity);  
    public async Task DeleteAsync(T entity) => _dbSet.Remove(entity);  
}  

其核心目标?关注点分离、提升可测试性、解耦业务逻辑与数据库细节


反对仓储模式:为何有人认为它已过时?

随着Entity Framework Core的普及,许多开发者认为仓储模式已冗余,原因如下:

  1. EF Core已内置仓储模式与工作单元模式
    EF Core提供开箱即用的功能:
    变更跟踪:无需手动调用Update()
    工作单元:自动管理事务
    抽象层:通过LINQ和DbContext处理数据库交互

如果你的仓储只是简单包装DbContext,无异于重复造轮子。

  1. 冗余抽象层 = 更高的复杂度
    若仓储方法仅转发调用至DbContext,意义何在?

反面教材——无用的仓储类:

public class UserRepository  
{  
    private readonly AppDbContext _context;  
    public UserRepository(AppDbContext context) => _context = context;  

    public async Task<User?> GetUserByIdAsync(int id)  
    {  
        return await _context.Users.FindAsync(id);  
    }  
}  

直接注入AppDbContext至服务层更简洁:

public class UserService  
{  
    private readonly AppDbContext _context;  
    public UserService(AppDbContext context) => _context = context;  

    public async Task<User?> GetUserByIdAsync(int id)  
    {  
        return await _context.Users.FindAsync(id);  
    }  
}  

为何要添加冗余层?

  1. 限制EF Core的完整能力
    EF Core的Include()AsNoTracking()和原生SQL等高级功能,可能在强制通过预定义仓储方法时失去灵活性。

支持仓储模式:为何它仍有生命力?

尽管争议不断,仓储模式在特定场景下仍不可替代。

  1. 数据库独立性
    若可能从SQL Server切换至MongoDB或PostgreSQL,抽象仓储可增强代码的未来适应性。
// 接口  
public interface IUserRepository  
{  
    Task<User?> GetByIdAsync(int id);  
}  

// EF Core实现  
public class EfUserRepository : IUserRepository  
{  
    private readonly AppDbContext _context;  
    public EfUserRepository(AppDbContext context) => _context = context;  
    public async Task<User?> GetByIdAsync(int id) => await _context.Users.FindAsync(id);  
}  

// MongoDB实现  
public class MongoUserRepository : IUserRepository  
{  
    private readonly IMongoCollection<User> _users;  
    public MongoUserRepository(IMongoClient client)  
    {  
        var database = client.GetDatabase("MyDatabase");  
        _users = database.GetCollection<User>("Users");  
    }  
    public async Task<User?> GetByIdAsync(int id)  
    {  
        return await _users.Find(u => u.Id == id).FirstOrDefaultAsync();  
    }  
}  

切换数据库变得轻而易举

  1. 可测试性:更简单的单元测试
    直接注入DbContext会因EF Core与内存数据库的兼容性问题导致单元测试困难。

通过仓储接口,可轻松模拟数据库层:

var mockRepo = new Mock<IUserRepository>();  
mockRepo.Setup(repo => repo.GetByIdAsync(1)).ReturnsAsync(new User { Id = 1, Name = "Ganesh Gurav" });  
var userService = new UserService(mockRepo.Object);  
var user = await userService.GetUserByIdAsync(1);  
Assert.Equal("Ganesh Gurav", user?.Name);  

若无仓储,则需依赖更复杂、低效的集成测试。

  1. 集中管理复杂查询
    在大型应用中,涉及多表连接、聚合或原生SQL的查询,可通过仓储封装以保持服务层整洁。
public class UserRepository : IUserRepository  
{  
    private readonly AppDbContext _context;  
    public UserRepository(AppDbContext context) => _context = context;  

    public async Task<IEnumerable<User>> GetUsersWithOrdersAsync()  
    {  
        return await _context.Users.Include(u => u.Orders).ToListAsync();  
    }  
}  

避免LINQ查询散落各处,降低维护成本


微软的官方立场是什么?

来源:Microsoft文档

使用自定义仓储 vs 直接使用EF DbContext
若项目足够简单,直接使用DbContext更高效;若需解耦或切换数据源,自定义仓储仍具价值。


是否应在.NET中使用仓储模式?

以下情况可跳过
• 仅使用EF Core且无换库计划
• 仓储仅包装DbContext而无实际价值
• 需完全访问EF Core高级功能

以下情况建议使用
• 未来可能切换数据库
• 需要更好的单元测试支持
• 需集中管理复杂查询


未消亡,但需明智使用

仓储模式并未消亡,但滥用会导致冗余复杂度。若仅用于包装DbContext,则得不偿失;若需数据库灵活性、测试支持或查询抽象,它仍是现代.NET应用的利器。

相关留言评论
昵称:
邮箱:
阅读排行