Abp 框架在其内部实现了仓储模式,并且支持 EF Core 与 Dapper 来进行数据库连接与管理,你可以很方便地通过注入仓储来操作你的数据。
例如:
public class TestAppService : ITransientDependency { private readonly IRepository<TestTable> _rep; public TestAppService(IRepository<TestTable> rep) { _rep = rep; } public void TestMethod() { // 插入一条新数据 _rep.Insert(new TestTable{ Name = "TestName" }); } } 1.仓储定义与实现在 Abp 内部,仓储的基本定义存放在 Abp 项目的 Domain/Repositories 内部,包括以下几个文件:
文件名称 作用描述AbpRepositoryBase.cs 仓储基类
AutoRepositoryTypesAttribute.cs 自动构建仓储,用于实体标记
IRepository.cs 仓储基本接口定义
IRepositoryOfTEntity.cs 仓储接口定义,默认主键为 int 类型
IRepositoryOfTEntityAndTPrimaryKey.cs 仓储接口定义,主键与实体类型由用户定义
ISupportsExplicitLoading.cs 显式加载
RepositoryExtensions.cs 仓储相关的扩展方法
1.1 仓储定义
综上所述,仓储的基础定义是由 IRepository 决定的,这个接口没什么其他用处,就如同 ITransientDependency 接口与 ISingletonDependency 一样,只是做一个标识作用。
真正定义了仓储接口的是在 IRepositoryOfTEntityAndTPrimaryKey<TEntity, TPrimaryKey> 内部,他的接口定义如下:
public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : class, IEntity<TPrimaryKey> { // CRUD 方法 }可以看到,他有两个泛型参数,第一个是实体类型,第二个是实体的主键类型,并且约束了 TEntity 必须实现了 IEntity<TPrimaryKey> 接口,这是因为在仓储接口内部的一些方法需要得到实体的主键才能够操作,比如修改与查询方法。
在 Abp 内部还有另外一个仓储的定义,叫做 IRepository<TEntity> ,这个接口就是默认你的主键类型为 int类型,一般很少使用 IRepository<TEntity, TPrimaryKey> 更多的还是用的 IRepository<TEntity>。
1.2 仓储的实现在 Abp 库里面,有一个默认的抽象基类实现了仓储接口,这个基类内部主要注入了 IUnitOfWorkManager 用来控制事务,还有 IIocResolver 用来解析 Ioc 容器内部注册的组件。
本身在这个抽象仓储类里面没有什么实质性的东西,它只是之前 IRepository<TEntity> 的简单实现,在 EfCoreRepositoryBase 类当中则才是具体调用 EF Core API 的实现。
public class EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey> : AbpRepositoryBase<TEntity, TPrimaryKey>, ISupportsExplicitLoading<TEntity, TPrimaryKey>, IRepositoryWithDbContext where TEntity : class, IEntity<TPrimaryKey> where TDbContext : DbContext { /// <summary> /// Gets EF DbContext object. /// </summary> public virtual TDbContext Context => _dbContextProvider.GetDbContext(MultiTenancySide); /// <summary> /// Gets DbSet for given entity. /// </summary> public virtual DbSet<TEntity> Table => Context.Set<TEntity>(); public virtual DbTransaction Transaction { get { return (DbTransaction) TransactionProvider?.GetActiveTransaction(new ActiveTransactionProviderArgs { {"ContextType", typeof(TDbContext) }, {"MultiTenancySide", MultiTenancySide } }); } } public virtual DbConnection Connection { get { var connection = Context.Database.GetDbConnection(); if (connection.State != ConnectionState.Open) { connection.Open(); } return connection; } } public IActiveTransactionProvider TransactionProvider { private get; set; } private readonly IDbContextProvider<TDbContext> _dbContextProvider; /// <summary> /// Constructor /// </summary> /// <param></param> public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider) { _dbContextProvider = dbContextProvider; } }其实从上方就可以看出来,Abp 对于每一个仓储都会重新打开一个数据库链接,在 EfCoreRepositoryBase 里面的 CRUD 方法实际上都是针对 DbContext 来进行的操作。
举个例子:
public override TEntity Insert(TEntity entity) { return Table.Add(entity).Entity; } public override TEntity Update(TEntity entity) { AttachIfNot(entity); Context.Entry(entity).State = EntityState.Modified; return entity; } protected virtual void AttachIfNot(TEntity entity) { var entry = Context.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity); if (entry != null) { return; } Table.Attach(entity); }这里需要注意的是 Update() 方法,之前遇到过一个问题,假如我传入了一个实体,它的 ID 是不存在的,那么我将这个实体传入 Update() 方法之后执行 SaveChanges() 的时候,会抛出 DbUpdateConcurrencyException 异常。
正确的操作是先使用实体的 ID 去查询数据库是否存在该条记录,存在再执行 Update() 操作。