NopCommerce 4.9.3全栈开发实战 - 2.3 仓储模式(Repository Pattern)实现
NopCommerce仓储模式采用IRepository<T>泛型接口实现数据访问解耦,核心组件包括IRepository<T>接口、EntityRepository<T>实现和BaseEntity基类。该模式通过统一接口封装CRUD操作,支持同步/异步方法,并提供带/不带跟踪的查询选项。具体实现基于Linq2DB,通过NopObjectContext与数据库交
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的仓储模式设计具有以下优势:
- **解耦业务逻辑与数据访)*:业务逻辑不直接依赖于Linq2DB,而是通过仓储接口访问数据
- *提供统一的数据访问接口:所有数据访问都通过仓储接口进行,便于管理和维护
- *支持多种数据库:可以轻松切换不同的数据源,如SQL Server、MySQL)4. 便于单元测试:可以通过Mock仓储接口来测试业务逻辑,无需连接数据库5. *实现数据访问的集中管理:数据访问逻辑集中在仓储层,便于修改和扩展
- 支持事件驱动架构:仓储操作会触发相应的事件,便于其他组件响应数据变化
- **提高代码的可读性和可维护)*:仓储接口提供了清晰的数据访问方法,便于理解和使)
7. 仓储模式最佳实现
在使用NopCommerce的仓储模式时,建议遵循以下最佳实践:
- **优先在服务层中使用仓)*:业务逻辑应该封装在服务层中,控制器直接调用服务层方法
- 使用合适的实体集合) - 对于只读查询,使用
TableNoTracking或TableUntracked,提高性能- 对于需要更新或删除的查询,使用
Table
- 对于需要更新或删除的查询,使用
- **应用适当的过滤条)*:在查询中应用过滤条件,减少返回的数据量
- 使用异步方法:优先使用异步方法,提高应用程序的响应)5. 避免在仓储层中实现业务逻辑:仓储层只负责数据访问,业务逻辑应该在服务层中实现6. 使用事务:对于涉及多个仓储操作的业务逻辑,使用事务确保数据一致)7. **实现软删)*:对于需要保留历史数据的实体,实现
ISoftDeleted接口,使用软删除 - 编写单元测试:为仓储操作编写单元测试,确保数据访问逻辑的正确)
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的事件驱动架构
更多推荐


所有评论(0)