使用 C# 简单介绍通用存储库模式

2025-06-09

使用 C# 简单介绍通用存储库模式

C# 中的通用存储库模式是一种抽象应用程序数据层的设计模式,使跨不同数据源和项目的数据访问逻辑管理更加便捷。它旨在通过在单个通用存储库中实现常见的数据操作(而不是为每种实体类型设置单独的存储库)来减少冗余。项目类型可以是桌面或 Web。当项目类型为 Web 时,需要考虑使用依赖注入来实现。在提供的示例中,我们使用控制台项目来覆盖更广泛的受众,而没有使用依赖注入,以便专注于如何编写通用存储库。

  • 通用存储库始于一个通用接口,该接口定义了一些常用操作,例如 CRUD 操作。这些操作以通用的方式定义,适用于任何实体类型。
  • 然后,通用接口在一个具体类中实现。该类处理数据源交互,例如使用 EF Core 或 Dapper 等 ORM 查询数据库。
  • 该实现通常利用 Entity Framework Core DbContext 或带有 IDbConnection 的 Dapper 与数据库进行交互。## 创建通用存储库

通用存储库将提供 CRUD 功能、选择所有记录、单个记录、插入新记录(可选择返回新主键)、更新和删除单个或批量记录集。

GitHub 源代码

🟢 无论代码是由单个开发人员还是开发人员团队使用,考虑应该包含什么以及命名约定都至关重要,因为这里的想法是在多个项目中使用该想法时的一致性。

首先定义一个基本接口,以便如果需要使用主键迭代数据,则将使用 Id 来保持一致性,并且它不属于通用接口的一部分。



public interface IBase 
{
    public int Id { get; }
}


Enter fullscreen mode Exit fullscreen mode

通用接口。如果您不介意方法名称,可以随意更改它们以及添加或删除方法。



public interface IGenericRepository<T> where T : class
{
    IEnumerable<T> GetAll();
    Task<List<T>> GetAllAsync();
    T GetById(int id);
    T GetByIdWithIncludes(int id);
    Task<T> GetByIdAsync(int id);
    Task<T> GetByIdWithIncludesAsync(int id);
    bool Remove(int id);
    void Add(in T sender);
    void Update(in T sender);
    int Save();
    Task<int> SaveAsync();
    public T Select(Expression<Func<T, bool>> predicate);
    public Task<T> SelectAsync(Expression<Func<T, bool>> predicate);
}


Enter fullscreen mode Exit fullscreen mode

将使用以下模型,一个将使用下面接口的所有方法创建,另外两个供读者练习。



public class Category : IBase
{
    public int Id => CategoryId;
    public int CategoryId { get; set; }
    public string? Name { get; set; }
    public virtual ICollection<Product>? Products { get; set; }
    public override string? ToString() => Name;
}

public partial class Countries : IBase
{
    public int Id => CountryId;
    [Key]
    public int CountryId { get; set; }
    public string Name { get; set; }
    public override string ToString() => Name;
}

public class Product : IBase
{
    public int Id => ProductId;
    public int ProductId { get; set; }
    public string? Name { get; set; }
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; } = null!;
    public override string? ToString() => Name;
}


Enter fullscreen mode Exit fullscreen mode

要实现生产操作的接口,请创建一个名为 ProductsRepository 的新类。按如下所示更改类签名。



public class ProductsRepository : IGenericRepository<Product>


Enter fullscreen mode Exit fullscreen mode

此时,Visual Studio 将提示您创建缺少的方法,请允许 Visual Studio 创建这些方法。每个方法都会被存根,以便您使用代码



public IEnumerable<Countries> GetAll()
{
    throw new NotImplementedException();
}


Enter fullscreen mode Exit fullscreen mode

由于此示例在同一个类项目中使用带有 DbContext 和 DbSets 的 EF Core,我们可以在此处初始化名为 Context 的 DbContext,如下所示,以便每次需要存储库时,DbContext 都已准备就绪。



public class ProductsRepository : IGenericRepository<Product>
{
    private Context _context;

    public ProductsRepository(Context context)
    {
        _context = context;
    }


Enter fullscreen mode Exit fullscreen mode

事后清理也是很好的,所以我们实现了 IDisposable。



public class ProductsRepository : IGenericRepository<Product>, 
    IDisposable
{

...

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}


Enter fullscreen mode Exit fullscreen mode

从这里开始,为每种方法编写代码,以便完成后得到类似这样的结果。



public class ProductsRepository : IGenericRepository<Product>, 
    IDisposable
{
    private Context _context;

    public ProductsRepository(Context context)
    {
        _context = context;
    }

    public IEnumerable<Product> GetAll()
    {
        return _context.Products.ToList();
    }

    public Task<List<Product>> GetAllAsync()
    {
        return _context.Products.ToListAsync();
    }

    public Product GetById(int id)
    {
        return _context.Products.Find(id);
    }

    public Product GetByIdWithIncludes(int id)
    {
        return _context.Products.Include(x => x.Category)
            .FirstOrDefault(x => x.ProductId == id);
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }

    public async Task<Product> GetByIdWithIncludesAsync(int id)
    {
        return await _context.Products.Include(x => x.Category)
            .FirstOrDefaultAsync(x => x.ProductId == id);
    }

    public bool Remove(int id)
    {
        var product = _context.Products.Find(id);
        if (product is { })
        {
            _context.Products.Remove(product);
            return true;
        }

        return false;
    }

    public void Add(in Product sender)
    {
        _context.Add(sender).State = EntityState.Added;
    }

    public void Update(in Product sender)
    {
        _context.Entry(sender).State = EntityState.Modified;
    }

    public int Save()
    {
        return _context.SaveChanges();
    }

    public Task<int> SaveAsync()
    {
        return _context.SaveChangesAsync();
    }

    public Product Select(
        Expression<Func<Product, bool>> predicate)
    {
        return _context.Products
            .WhereNullSafe(predicate).FirstOrDefault()!;
    }

    public async Task<Product> SelectAsync(
        Expression<Func<Product, bool>> predicate)
    {
        return 
            (
                await _context.Products
                    .WhereNullSafe(predicate).FirstOrDefaultAsync())!;
    }
    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}


Enter fullscreen mode Exit fullscreen mode

上面缺少什么?您可以添加更多方法,例如,您的数据库模型包含订单和订单详情,并且只需要特定订单的产品,只需添加该方法即可。

把十个开发人员放在一个房间里,问他们如何使用通用存储库,我们很可能会得到至少三个不同的版本。

以下内容来自一个随机的Stackoverflow 帖子。请注意,它们的界面与上面显示的界面类似。



public interface IRepositoryBase<T> where T : class
{
    void Add(T objModel);
    void AddRange(IEnumerable<T> objModel);
    T? GetId(int id);
    Task<T?> GetIdAsync(int id);
    T? Get(Expression<Func<T, bool>> predicate);
    Task<T?> GetAsync(Expression<Func<T, bool>> predicate);
    IEnumerable<T> GetList(Expression<Func<T, bool>> predicate);
    Task<IEnumerable<T>> GetListAsync(Expression<Func<T, bool>> predicate);
    IEnumerable<T> GetAll();
    Task<IEnumerable<T>> GetAllAsync();
    int Count();
    Task<int> CountAsync();
    void Update(T objModel);
    void Remove(T objModel);
    void Dispose();
}


Enter fullscreen mode Exit fullscreen mode

然后有一个基础存储库,与上面显示的不同,它用于 ORM 中的所有实体。



public class RepositoryBase<TEntity> : IRepositoryBase<TEntity> where TEntity : class
{

    protected readonly Context _context = new();

    public void Add(TEntity model)
    {
        _context.Set<TEntity>().Add(model);
        _context.SaveChanges();
    }

    public void AddRange(IEnumerable<TEntity> model)
    {
        _context.Set<TEntity>().AddRange(model);
        _context.SaveChanges();
    }

    public TEntity? GetId(int id)
    {
        return _context.Set<TEntity>().Find(id);
    }

    public async Task<TEntity?> GetIdAsync(int id)
    {
        return await _context.Set<TEntity>().FindAsync(id);
    }

    public TEntity? Get(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>().FirstOrDefault(predicate);
    }

    public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await _context.Set<TEntity>().FirstOrDefaultAsync(predicate);
    }

    public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>().Where<TEntity>(predicate).ToList();
    }

    public async Task<IEnumerable<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await Task.Run(() => _context.Set<TEntity>().Where<TEntity>(predicate));
    }

    public IEnumerable<TEntity> GetAll()
    {
        return _context.Set<TEntity>().ToList();
    }

    public async Task<IEnumerable<TEntity>> GetAllAsync()
    {
        return await Task.Run(() => _context.Set<TEntity>());
    }

    public int Count()
    {
        return _context.Set<TEntity>().Count();
    }

    public async Task<int> CountAsync()
    {
        return await _context.Set<TEntity>().CountAsync();
    }

    public void Update(TEntity objModel)
    {
        _context.Entry(objModel).State = EntityState.Modified;
        _context.SaveChanges();
    }

    public void Remove(TEntity objModel)
    {
        _context.Set<TEntity>().Remove(objModel);
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}


Enter fullscreen mode Exit fullscreen mode

使用哪种模式?这取决于个人喜好和经验。

运行提供的代码

项目 WorkingWithInterfacesApp1 在 appsettings.json 中定义了使用 .\SQLEXPRESS 作为服务器的连接字符串。如果此服务器可用,请保留原样,否则请将其更改为其他可用的服务器。

  1. DbContext 中的代码将创建数据库并填充 DbContext 中定义的表。
  2. 使用 ProductsRepository
    1. 使用 GetAll 方法获取所有产品
    2. 使用 GetByIdWithIncludeAsync 获取产品
    3. 删除id为4的商品
    4. 使用 Add 方法添加新产品
    5. 保存更改并检查保存更改返回的 2 个结果,一个用于删除,一个用于添加。
    6. 编辑产品
    7. 保存更改。

演示代码的结果

展翅高飞

研究产品代码,然后尝试使用其他模型之一或采用这两个接口并在您的项目中尝试。

不使用 EF Core

通用存储库模式将与 Dapper 一起使用实例或使用 DataTable 的连接和命令对象。

源代码

克隆以下GitHub 存储库

参见

泛型简介(C#)

鏂囩珷鏉ユ簮锛�https://dev.to/karenpayneoregon/gentle-introduction-to-generic-repository-pattern-with-c-1jn0
PREV
隐藏的 NET 9 宝石
NEXT
将 React 应用部署到 S3 和 Cloudfront