1. 仓储模式概述

仓储模式是一种数据访问模式,它将数据访问逻辑封装在仓储类中,提供统一的接口来访问数据,从而实现业务逻辑与数据访问的解耦。仓储模式具有以下优势:

  • *解耦业务逻辑与数据访:业务逻辑不直接依赖于具体的数据访问实现- *提供统一的数据访问接口:所有数据访问都通过仓储接口进行,便于管理和维护
  • *支持多种数据库:可以轻松切换不同的数据源,如SQL Server、MySQL- 便于单元测试:可以通过Mock仓储接口来测试业务逻辑
  • *实现数据访问的集中管理:数据访问逻辑集中在仓储层,便于修改和扩展

NopCommerce广泛使用仓储模式,将所有数据访问操作封装在仓储类中,业务逻辑层通过仓储接口访问数据库

2. NopCommerce仓储模式设计

NopCommerce的仓储模式设计基于以下核心组件:

  • IRepository:泛型仓储接口,定义了数据访问的基本操作
  • EntityRepository:IRepository的实现,基于Linq2DB
  • BaseEntity:所有实体的基类,包含Id属性- NopObjectContext:Linq2DB的数据上下文,负责与数据库交

3. IRepository接口详解

IRepository是NopCommerce仓储模式的核心接口,定义了数据访问的基本操作)

3.1 核心接口定义

// IRepository.cs - 泛型仓储接口
public partial interface IRepository<TEntity> where TEntity : BaseEntity
{
    // 获取实体
    Task<TEntity> GetByIdAsync(int id, bool includeDeleted = true);
    TEntity GetById(int id, bool includeDeleted = true);
    
    // 获取所有实现    IQueryable<TEntity> Table { get; }
    IQueryable<TEntity> TableNoTracking { get; }
    IQueryable<TEntity> TableUntracked { get; }
    
    // 插入实体
    Task InsertAsync(TEntity entity, bool publishEvent = true);
    void Insert(TEntity entity, bool publishEvent = true);
    
    // 更新实体
    Task UpdateAsync(TEntity entity, bool publishEvent = true);
    void Update(TEntity entity, bool publishEvent = true);
    
    // 删除实体
    Task DeleteAsync(TEntity entity, bool publishEvent = true);
    void Delete(TEntity entity, bool publishEvent = true);
    
    // 批量操作
    Task InsertAsync(IEnumerable<TEntity> entities, bool publishEvent = true);
    void Insert(IEnumerable<TEntity> entities, bool publishEvent = true);
    Task UpdateAsync(IEnumerable<TEntity> entities, bool publishEvent = true);
    void Update(IEnumerable<TEntity> entities, bool publishEvent = true);
    Task DeleteAsync(IEnumerable<TEntity> entities, bool publishEvent = true);
    void Delete(IEnumerable<TEntity> entities, bool publishEvent = true);
}

3.2 接口方法说明

方法 主要职责 参数说明
GetByIdAsync 根据Id异步获取实体 id:实体Id
includeDeleted:是否包含已删除实体
GetById 根据Id同步获取实体 id:实体Id
includeDeleted:是否包含已删除实体
Table 获取可查询的实体集合(带跟踪)
TableNoTracking 获取可查询的实体集合(不带跟踪)
TableUntracked 获取可查询的实体集合(完全不带跟踪)
InsertAsync 异步插入实体 entity:要插入的实现br>publishEvent:是否发布事)
Insert 同步插入实体 entity:要插入的实现br>publishEvent:是否发布事)
UpdateAsync 异步更新实体 entity:要更新的实现br>publishEvent:是否发布事)
Update 同步更新实体 entity:要更新的实现br>publishEvent:是否发布事)
DeleteAsync 异步删除实体 entity:要删除的实现br>publishEvent:是否发布事)
Delete 同步删除实体 entity:要删除的实现br>publishEvent:是否发布事)

4. EntityRepository实现

EntityRepository是IRepository接口的实现,基于Linq2DB)

4.1 核心实现代码

// EntityRepository.cs - 仓储接口实现
public partial class EntityRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
{
    private readonly NopObjectContext _objectContext;
    private readonly IEventPublisher _eventPublisher;
    private readonly IDbContext _dbContextImplementation;
    private readonly IMemoryCache _memoryCache;
    
    public EntityRepository(NopObjectContext objectContext, IEventPublisher eventPublisher, IMemoryCache memoryCache)
    {
        _objectContext = objectContext;
        _eventPublisher = eventPublisher;
        _memoryCache = memoryCache;
        _dbContextImplementation = (IDbContext)objectContext;
    }
    
    // Table属性实现    public virtual IQueryable<TEntity> Table => GetAllAsync(includeDeleted: true).Result;
    
    // TableNoTracking属性实现    public virtual IQueryable<TEntity> TableNoTracking => GetAllAsync(includeDeleted: true).Result.AsNoTracking();
    
    // TableUntracked属性实现    public virtual IQueryable<TEntity> TableUntracked => GetAllAsync(includeDeleted: true).Result.AsNoTracking();
    
    // GetByIdAsync方法实现
    public virtual async Task<TEntity> GetByIdAsync(int id, bool includeDeleted = true)
    {
        if (id == 0)
            return null;
        
        return await GetAllAsync(includeDeleted: includeDeleted)
            .FirstOrDefaultAsync(x => x.Id == id);
    }
    
    // GetById方法实现
    public virtual TEntity GetById(int id, bool includeDeleted = true)
    {
        if (id == 0)
            return null;
        
        return GetAllAsync(includeDeleted: includeDeleted)
            .Result.FirstOrDefault(x => x.Id == id);
    }
    
    // InsertAsync方法实现
    public virtual async Task InsertAsync(TEntity entity, bool publishEvent = true)
    {
        ArgumentNullException.ThrowIfNull(entity);
        
        await _objectContext.InsertAsync(entity);
        await _objectContext.SaveChangesAsync();
        
        // 发布实体插入事件
        if (publishEvent)
            await _eventPublisher.PublishAsync(new EntityInsertedEvent<TEntity>(entity));
    }
    
    // UpdateAsync方法实现
    public virtual async Task UpdateAsync(TEntity entity, bool publishEvent = true)
    {
        ArgumentNullException.ThrowIfNull(entity);
        
        await _objectContext.UpdateAsync(entity);
        await _objectContext.SaveChangesAsync();
        
        // 发布实体更新事件
        if (publishEvent)
            await _eventPublisher.PublishAsync(new EntityUpdatedEvent<TEntity>(entity));
    }
    
    // DeleteAsync方法实现
    public virtual async Task DeleteAsync(TEntity entity, bool publishEvent = true)
    {
        ArgumentNullException.ThrowIfNull(entity);
        
        await _objectContext.DeleteAsync(entity);
        await _objectContext.SaveChangesAsync();
        
        // 发布实体删除事件
        if (publishEvent)
            await _eventPublisher.PublishAsync(new EntityDeletedEvent<TEntity>(entity));
    }
    
    // 批量操作方法实现...
    
    // 辅助方法
    protected virtual async Task<IQueryable<TEntity>> GetAllAsync(bool includeDeleted = true)
    {
        var query = _objectContext.GetTable<TEntity>().AsQueryable();
        
        // 如果实体实现了ISoftDeleted接口,且includeDeleted为false,则过滤已删除实现        if (typeof(ISoftDeleted).IsAssignableFrom(typeof(TEntity)) && !includeDeleted)
            query = query.Where(x => !((ISoftDeleted)x).Deleted);
        
        return await Task.FromResult(query);
    }
}

4.2 实体跟踪

Linq2DB支持实体跟踪,用于检测实体的变化并自动更新数据库。NopCommerce的仓储接口提供了三种不同的实体集合:

属性 跟踪状) 用途
Table 带跟) 用于需要更新或删除的实体查)
TableNoTracking 不带跟踪 用于只读查询,提高性能
TableUntracked 完全不带跟踪 用于不需要更改跟踪的查询,性能最)

5. 仓储模式的使)

在NopCommerce中,业务逻辑层通过依赖注入获取仓储接口,然后使用仓储接口访问数据库

5.1 在服务层中使用仓)

// ProductService.cs - 产品服务实现
public partial class ProductService : IProductService
{
    private readonly IRepository<Product> _productRepository;
    private readonly IRepository<ProductCategory> _productCategoryRepository;
    private readonly IEventPublisher _eventPublisher;
    
    // 通过构造函数注入仓)    public ProductService(IRepository<Product> productRepository,
        IRepository<ProductCategory> productCategoryRepository,
        IEventPublisher eventPublisher)
    {
        _productRepository = productRepository;
        _productCategoryRepository = productCategoryRepository;
        _eventPublisher = eventPublisher;
    }
    
    // 使用仓储获取产品
    public async Task<Product> GetProductByIdAsync(int productId, bool includeDeleted = false)
    {
        return await _productRepository.GetByIdAsync(productId, includeDeleted);
    }
    
    // 使用仓储获取产品列表
    public async Task<IPagedList<Product>> GetAllProductsAsync(string productName = null,
        int categoryId = 0, int manufacturerId = 0, int storeId = 0, int vendorId = 0,
        int warehouseId = 0, int productTypeId = 0, int productTemplateId = 0,
        bool showHidden = false, int pageIndex = 0, int pageSize = int.MaxValue,
        bool overridePublished = false)
    {
        // 获取查询对象
        var query = _productRepository.Table;
        
        // 应用过滤条件
        if (!string.IsNullOrEmpty(productName))
            query = query.Where(p => p.Name.Contains(productName));
        
        if (categoryId > 0)
        {
            query = from p in query
                    join pc in _productCategoryRepository.Table on p.Id equals pc.ProductId
                    where pc.CategoryId == categoryId
                    select p;
        }
        
        // 应用分页
        var products = await query.ToPagedListAsync(pageIndex, pageSize);
        
        return products;
    }
    
    // 使用仓储插入产品
    public async Task InsertProductAsync(Product product)
    {
        await _productRepository.InsertAsync(product);
    }
    
    // 使用仓储更新产品
    public async Task UpdateProductAsync(Product product)
    {
        await _productRepository.UpdateAsync(product);
    }
    
    // 使用仓储删除产品
    public async Task DeleteProductAsync(Product product)
    {
        await _productRepository.DeleteAsync(product);
    }
}

5.2 在控制器中使用仓)

虽然建议在服务层中使用仓储,但在某些情况下,也可以在控制器中直接使用仓储)

// ProductController.cs - 产品控制)public partial class ProductController : BasePublicController
{
    private readonly IRepository<Product> _productRepository;
    private readonly IProductModelFactory _productModelFactory;
    
    // 通过构造函数注入仓)    public ProductController(IRepository<Product> productRepository,
        IProductModelFactory productModelFactory)
    {
        _productRepository = productRepository;
        _productModelFactory = productModelFactory;
    }
    
    public async Task<IActionResult> ProductDetails(int productId)
    {
        // 使用仓储获取产品
        var product = await _productRepository.GetByIdAsync(productId);
        if (product == null || !product.Published)
            return InvokeHttp404();
        
        // 其他逻辑...
    }
}

6. 仓储模式的优化

NopCommerce的仓储模式设计具有以下优势:

  1. **解耦业务逻辑与数据访)*:业务逻辑不直接依赖于Linq2DB,而是通过仓储接口访问数据
  2. *提供统一的数据访问接口:所有数据访问都通过仓储接口进行,便于管理和维护
  3. *支持多种数据库:可以轻松切换不同的数据源,如SQL Server、MySQL)4. 便于单元测试:可以通过Mock仓储接口来测试业务逻辑,无需连接数据库5. *实现数据访问的集中管理:数据访问逻辑集中在仓储层,便于修改和扩展
  4. 支持事件驱动架构:仓储操作会触发相应的事件,便于其他组件响应数据变化
  5. **提高代码的可读性和可维护)*:仓储接口提供了清晰的数据访问方法,便于理解和使)

7. 仓储模式最佳实现

在使用NopCommerce的仓储模式时,建议遵循以下最佳实践:

  1. **优先在服务层中使用仓)*:业务逻辑应该封装在服务层中,控制器直接调用服务层方法
  2. 使用合适的实体集合) - 对于只读查询,使用TableNoTrackingTableUntracked,提高性能
    • 对于需要更新或删除的查询,使用Table
  3. **应用适当的过滤条)*:在查询中应用过滤条件,减少返回的数据量
  4. 使用异步方法:优先使用异步方法,提高应用程序的响应)5. 避免在仓储层中实现业务逻辑:仓储层只负责数据访问,业务逻辑应该在服务层中实现6. 使用事务:对于涉及多个仓储操作的业务逻辑,使用事务确保数据一致)7. **实现软删)*:对于需要保留历史数据的实体,实现ISoftDeleted接口,使用软删除
  5. 编写单元测试:为仓储操作编写单元测试,确保数据访问逻辑的正确)

8. 软删除实现

NopCommerce支持软删除功能,通过ISoftDeleted接口实现。软删除不会物理删除数据,而是将Deleted属性设置为true

8.1 ISoftDeleted接口

// ISoftDeleted.cs - 软删除接口public partial interface ISoftDeleted
{
    /// <summary>
    /// Gets or sets a value indicating whether the entity has been deleted
    /// </summary>
    bool Deleted { get; set; }
}

8.2 实现软删除的实体

// Product.cs - 产品实体,实现ISoftDeleted接口
public partial class Product : BaseEntity, ISoftDeleted
{
    // 其他属性..
    
    /// <summary>
    /// Gets or sets a value indicating whether the product has been deleted
    /// </summary>
    public bool Deleted { get; set; }
    
    /// <summary>
    /// Gets or sets a value indicating whether the product is published
    /// </summary>
    public bool Published { get; set; }
    
    // 其他属性..
}

8.3 软删除的工作原理

EntityRepository<T>GetAllAsync方法中,会检查实体是否实现了ISoftDeleted接口,如果实现了且includeDeleted参数为false,则会过滤掉已删除的实体)

protected virtual async Task<IQueryable<TEntity>> GetAllAsync(bool includeDeleted = true)
{
    var query = _objectContext.GetTable<TEntity>().AsQueryable();
    
    // 如果实体实现了ISoftDeleted接口,且includeDeleted为false,则过滤已删除实现    if (typeof(ISoftDeleted).IsAssignableFrom(typeof(TEntity)) && !includeDeleted)
        query = query.Where(x => !((ISoftDeleted)x).Deleted);
    
    return await Task.FromResult(query);
}

9. 总结

仓储模式是NopCommerce数据访问层的核心,它将所有数据访问操作封装在仓储类中,业务逻辑层通过仓储接口访问数据。这种设计实现了业务逻辑与数据访问的解耦,提高了代码的可维护性、可测试性和可扩展性能
NopCommerce的仓储模式基于泛型接口设计,支持多种数据源,提供了丰富的数据访问方法,包括单实体操作和批量操作。同时,它还支持实体跟踪、软删除和事件驱动架构等高级功能)
在开发NopCommerce应用时,建议遵循仓储模式的最佳实践,优先在服务层中使用仓储,使用合适的实体集合,应用适当的过滤条件,使用异步方法,避免在仓储层中实现业务逻辑,使用事务确保数据一致性,实现软删除,并编写单元测试)
下一篇文章将详细介绍NopCommerce的事件发)订阅系统,帮助开发者深入理解NopCommerce的事件驱动架构
储模式是NopCommerce数据访问层的核心,它将所有数据访问操作封装在仓储类中,业务逻辑层通过仓储接口访问数据。这种设计实现了业务逻辑与数据访问的解耦,提高了代码的可维护性、可测试性和可扩展性能
NopCommerce的仓储模式基于泛型接口设计,支持多种数据源,提供了丰富的数据访问方法,包括单实体操作和批量操作。同时,它还支持实体跟踪、软删除和事件驱动架构等高级功能)
在开发NopCommerce应用时,建议遵循仓储模式的最佳实践,优先在服务层中使用仓储,使用合适的实体集合,应用适当的过滤条件,使用异步方法,避免在仓储层中实现业务逻辑,使用事务确保数据一致性,实现软删除,并编写单元测试)
下一篇文章将详细介绍NopCommerce的事件发)订阅系统,帮助开发者深入理解NopCommerce的事件驱动架构

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐