这里的两个 GetRepositoryType() 都是抽象方法,具体的实现分别在 EfCoreRepositoryRegistrar、MemoryDbRepositoryRegistrar、MongoDbRepositoryRegistrar 的内部,这里我们只讲 EF Core 相关的。
protected override Type GetRepositoryType(Type dbContextType, Type entityType) { return typeof(EfCoreRepository<,>).MakeGenericType(dbContextType, entityType); }可以看到,在方法内部是构造了一个 EfCoreRepository 类型作为默认仓储的实现。
2.3 数据库上下文提供者在 Ef Core 仓储的内部,需要操作数据库时,必须要获得一个数据库上下文。在仓储内部的数据库上下文都是由 IDbContextProvider<TDbContext> 提供了,这个东西在 EF Core 模块初始化的时候就已经被注册,它的默认实现是 UnitOfWorkDbContextProvider<TDbContext>。
public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity> where TDbContext : IEfCoreDbContext where TEntity : class, IEntity { public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>(); DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>(); protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext(); // ... private readonly IDbContextProvider<TDbContext> _dbContextProvider; // ... public EfCoreRepository(IDbContextProvider<TDbContext> dbContextProvider) { _dbContextProvider = dbContextProvider; // ... } // ... }首先来看一下这个实现类的基本定义,比较简单,注入了两个接口,分别用于获取工作单元和构造 DbContext。需要注意的是,这里通过 where 约束来指定 TDbContext 必须实现 IEfCoreDbContext 接口。
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : IEfCoreDbContext { private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConnectionStringResolver _connectionStringResolver; public UnitOfWorkDbContextProvider( IUnitOfWorkManager unitOfWorkManager, IConnectionStringResolver connectionStringResolver) { _unitOfWorkManager = unitOfWorkManager; _connectionStringResolver = connectionStringResolver; } // ... }接着想下看,接口只定义了一个方法,就是 GetDbContext(),在这个默认实现里面,首先会从缓存里面获取数据库上下文,如果没有获取到,则创建一个新的数据库上下文。
public TDbContext GetDbContext() { // 获得当前的可用工作单元。 var unitOfWork = _unitOfWorkManager.Current; if (unitOfWork == null) { throw new AbpException("A DbContext can only be created inside a unit of work!"); } // 获得数据库连接上下文的连接字符串名称。 var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>(); // 根据名称解析具体的连接字符串。 var connectionString = _connectionStringResolver.Resolve(connectionStringName); // 构造数据库上下文缓存 Key。 var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; // 从工作单元的缓存当中获取数据库上下文,不存在则调用 CreateDbContext() 创建。 var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, () => new EfCoreDatabaseApi<TDbContext>( CreateDbContext(unitOfWork, connectionStringName, connectionString) )); return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext; }回到最开始的数据库上下文配置工厂,在它的内部会优先从一个 Current 获取一个 DbContextCreationContext 实例。而在这里,就是 Current 被赋值的地方,只要调用了 Use() 方法,在释放之前都会获取到同一个实例。
private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) { var creationContext = new DbContextCreationContext(connectionStringName, connectionString); using (DbContextCreationContext.Use(creationContext)) { // 这里是重点,真正创建数据库上下文的地方。 var dbContext = CreateDbContext(unitOfWork); if (unitOfWork.Options.Timeout.HasValue && dbContext.Database.IsRelational() && !dbContext.Database.GetCommandTimeout().HasValue) { dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To<int>()); } return dbContext; } } // 如果是事务型的工作单元,则调用 CreateDbContextWithTransaction() 进行创建,但不论如何都是通过工作单元提供的 IServiceProvider 解析出来 DbContext 的。 private TDbContext CreateDbContext(IUnitOfWork unitOfWork) { return unitOfWork.Options.IsTransactional ? CreateDbContextWithTransaction(unitOfWork) : unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); }