ABP系列,這個系列來的比較晚,很多大佬其實已經分析過,為什么現在我又來一輪呢?
1.想自己來完整的學習一輪ABP
2.公司目前正在使用ABP,准備遷移Core
基於以上的目的,開始這個系列 😁
ABP IRepository
先上 IRepository 類圖結構
只是描述了類的關聯關系,很多成員並不准確 😄
基於這個類圖,我們再來分析下ABP的倉儲訪問;
1.IRepository 整體結構
按照我的理解,可以簡單分為三部分;
1.整體接口以及抽象父類定義
2.自定義DbContext,Repository,實體
3.自動注冊實體倉儲
1.整體接口以及抽象父類定義
這部分內容整體包含在IRepository,IRepository<TEntity,TprimaryKey>,AbpRepositoryBase中,也就是圖中為包含在虛線框的內容;
IRepository:倉儲的接口,接口中未定義方方法
IRepository<TEntity, TPrimaryKey> :定義倉儲對象的相關查詢方法,GetAll(),Get()等方法
AbpRepositoryBase<TEntity, TPrimaryKey> :抽象類,封裝了一些公共方法但是並未有具體實現,實現留在了具體的調用層,例如 EF,EfCore,Dapper等
接口實現
EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey> :
實現AbpRepositoryBase<TEntity, TPrimaryKey>
1.EFCore的內部核心查詢全部就依賴於 DbContext,DbSet來操作數據;
2.EFCore的DbContext引用來源Microsoft.EntityFrameworkCore.DbContext,而Ef的DbContext依賴引用System.Data.Entity.DbContext,Core的底層依賴就全部替換了
AbpDbContext :ABP默認的EFCore的DBContext封裝,包含一些公共方法,要在ABP框架下使用DbContext,需要繼承 AbpDbContext
2.自定義DbContext,Repository,實體
實現DBContext:
public class SampleAppDbContext : AbpZeroDbContext<Tenant, Role, User, SampleAppDbContext>, IAbpPersistedGrantDbContext
{
public DbSet<PersistedGrantEntity> PersistedGrants { get; set; }
public DbSet<Advertisement> Advertisements { get; set; }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<ProductTranslation> ProductTranslations { get; set; }
public DbSet<Author> Authors { get; set; }
public DbSet<Store> Stores { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderTranslation> OrderTranslations { get; set; }
public DbSet<UserTestEntity> UserTestEntities { get; set; }
public DbSet<Country> Countries { get; set; }
public SampleAppDbContext(DbContextOptions<SampleAppDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigurePersistedGrantEntity();
modelBuilder.Entity<Blog>().OwnsOne(x => x.More);
modelBuilder.Entity<Blog>().OwnsMany(x => x.Promotions, b =>
{
b.WithOwner().HasForeignKey(bp => bp.BlogId);
b.Property<int>("Id");
b.HasKey("Id");
b.HasOne<Blog>()
.WithOne()
.HasForeignKey<BlogPromotion>(bp => bp.AdvertisementId)
.IsRequired();
});
modelBuilder.Entity<Advertisement>().OwnsMany(a => a.Feedbacks, b =>
{
b.WithOwner().HasForeignKey(af => af.AdvertisementId);
b.Property<int>("Id");
b.HasKey("Id");
b.HasOne<Comment>()
.WithOne()
.HasForeignKey<AdvertisementFeedback>(af => af.CommentId);
});
modelBuilder.Entity<Book>().ToTable("Books");
modelBuilder.Entity<Book>().Property(e => e.Id).ValueGeneratedNever();
modelBuilder.Entity<Store>().Property(e => e.Id).HasColumnName("StoreId");
}
}
}
DbContext中需要定義實體的DBSet,因為數據操作都是基於DbSet來完成
個性化倉儲:
第一步,設置自定義倉儲接口
public interface IPostRepository : IRepository<Post, Guid>
{
}
這里繼承IRepository<Entity,PrimaryKey>,說明實體主鍵並非Int類型,所以需要重新實現
第二步,繼承 EfCoreRepositoryBase,實現自定義倉儲方法
public class PostRepository : EfCoreRepositoryBase<BloggingDbContext, Post, Guid>,
IPostRepository
{
public PostRepository(IDbContextProvider<BloggingDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public override int Count()
{
throw new Exception("can not get count of posts");
}
}
第三步,注冊自定義倉儲,注冊代碼寫在自定義模塊中
注意:自定義模塊的注冊必須依賴 AbpEntityFrameworkCoreModule 模塊先注冊 ❓ 這里留着后面來解釋,為什么一定要依賴
//Custom repository
Configuration.ReplaceService<IRepository<Post, Guid>>(() =>
{
IocManager.IocContainer.Register(
Component.For<IRepository<Post, Guid>, IPostRepository,
PostRepository>()
.ImplementedBy<PostRepository>()
.LifestyleTransient()
);
});
3.自動注冊實體倉儲
首先來看下,我們定義好DbContext后,如果使用自己的倉儲服務呢?
在類里面定義屬性倉儲
private readonly IRepository<EntityDynamicParameter> _entityDynamicParameterRepository;
大家有沒有考慮過,為什么我們可以直接使用實體的倉儲類,在哪里實例化的呢? 這是ABP自動完成的,會反射獲取所有的實體服務,並自動為其注冊倉儲服務,我們一起來分析下自動注冊的內容
AbpEntityFrameworkCoreModule.cs
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(AbpEntityFrameworkCoreModule).GetAssembly());
IocManager.IocContainer.Register(
Component.For(typeof(IDbContextProvider<>))
.ImplementedBy(typeof(UnitOfWorkDbContextProvider<>))
.LifestyleTransient()
);
RegisterGenericRepositoriesAndMatchDbContexes();
}
調用 RegisterGenericRepositoriesAndMatchDbContexes 方法
private void RegisterGenericRepositoriesAndMatchDbContexes()
{
var dbContextTypes =
_typeFinder.Find(type =>
{
var typeInfo = type.GetTypeInfo();
return typeInfo.IsPublic &&
!typeInfo.IsAbstract &&
typeInfo.IsClass &&
typeof(AbpDbContext).IsAssignableFrom(type);
});
if (dbContextTypes.IsNullOrEmpty())
{
Logger.Warn("No class found derived from AbpDbContext.");
return;
}
using (IScopedIocResolver scope = IocManager.CreateScope())
{
foreach (var dbContextType in dbContextTypes)
{
Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName);
scope.Resolve<IEfGenericRepositoryRegistrar>().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default);
IocManager.IocContainer.Register(
Component.For<ISecondaryOrmRegistrar>()
.Named(Guid.NewGuid().ToString("N"))
.Instance(new EfCoreBasedSecondaryOrmRegistrar(dbContextType, scope.Resolve<IDbContextEntityFinder>()))
.LifestyleTransient()
);
}
scope.Resolve<IDbContextTypeMatcher>().Populate(dbContextTypes);
}
}
1.首先加載所有的AbpDbContext
2.對AbpDbContext循環進行注冊
這里的注冊依賴接口
scope.Resolve().RegisterForDbContext(dbContextType, IocManager, EfCoreAutoRepositoryTypes.Default);
我們來看下這個具體實現邏輯,依賴接口 IEfGenericRepositoryRegistrar
EfGenericRepositoryRegistrar.cs
public void RegisterForDbContext(
Type dbContextType,
IIocManager iocManager,
AutoRepositoryTypesAttribute defaultAutoRepositoryTypesAttribute)
{
var autoRepositoryAttr = dbContextType.GetTypeInfo().GetSingleAttributeOrNull<AutoRepositoryTypesAttribute>() ?? defaultAutoRepositoryTypesAttribute;
RegisterForDbContext(
dbContextType,
iocManager,
autoRepositoryAttr.RepositoryInterface,
autoRepositoryAttr.RepositoryInterfaceWithPrimaryKey,
autoRepositoryAttr.RepositoryImplementation,
autoRepositoryAttr.RepositoryImplementationWithPrimaryKey
);
if (autoRepositoryAttr.WithDefaultRepositoryInterfaces)
{
RegisterForDbContext(
dbContextType,
iocManager,
defaultAutoRepositoryTypesAttribute.RepositoryInterface,
defaultAutoRepositoryTypesAttribute.RepositoryInterfaceWithPrimaryKey,
autoRepositoryAttr.RepositoryImplementation,
autoRepositoryAttr.RepositoryImplementationWithPrimaryKey
);
}
}
private void RegisterForDbContext(
Type dbContextType,
IIocManager iocManager,
Type repositoryInterface,
Type repositoryInterfaceWithPrimaryKey,
Type repositoryImplementation,
Type repositoryImplementationWithPrimaryKey)
{
foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType))
{
var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
if (primaryKeyType == typeof(int))
{
var genericRepositoryType = repositoryInterface.MakeGenericType(entityTypeInfo.EntityType);
if (!iocManager.IsRegistered(genericRepositoryType))
{
var implType = repositoryImplementation.GetGenericArguments().Length == 1 ? repositoryImplementation.MakeGenericType(entityTypeInfo.EntityType) : repositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType,
entityTypeInfo.EntityType);
iocManager.IocContainer.Register(
Component
.For(genericRepositoryType)
.ImplementedBy(implType)
.Named(Guid.NewGuid().ToString("N"))
.LifestyleTransient()
);
}
}
var genericRepositoryTypeWithPrimaryKey =
repositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType,primaryKeyType);
if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey))
{
var implType =
repositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2? repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType) : repositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType);
iocManager.IocContainer.Register(
Component
.For(genericRepositoryTypeWithPrimaryKey)
.ImplementedBy(implType)
.Named(Guid.NewGuid().ToString("N"))
.LifestyleTransient()
);
}
}
}
來分析下具體的實現邏輯
foreach (var entityTypeInfo in _dbContextEntityFinder.GetEntityTypeInfos(dbContextType))
_dbContextEntityFinder.GetEntityTypeInfos(dbContextType) 這里獲取的就是DbContext定義的實體DbSet,從而獲取到每個實體,用來做后續的倉儲注入;例如:獲取到了 PersonEntity
var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType);
獲取實體主鍵
if (primaryKeyType == typeof(int))
判斷主鍵是否為int,如果是int,則繼承 IRepository
那是通過什么類來實現的IRepository呢?
public static AutoRepositoryTypesAttribute Default { get; }
static EfCoreAutoRepositoryTypes()
{
Default = new AutoRepositoryTypesAttribute(
typeof(IRepository<>),
typeof(IRepository<,>),
typeof(EfCoreRepositoryBase<,>),
typeof(EfCoreRepositoryBase<,,>)
);
}
這是默認的實體繼承的倉儲類,EfCoreRepositoryBase 類
好了,實體的默認倉儲就介紹完畢了。。。 😔 不對啊,這里可以滿足我們的DbContext里面所有的實體,但是萬一有了自定義倉儲呢?怎么注冊自己的倉儲呢?
哈哈,其實還是有個方法的,而且還不只一個。。。
1.DbContext打標記,用來替換默認的AutoRepositoryTypesAttribute
[AutoRepositoryTypes(
typeof(IMyModuleRepository<>),
typeof(IMyModuleRepository<,>),
typeof(MyModuleRepositoryBase<>),
typeof(MyModuleRepositoryBase<,>)
)]
public class SupportDbContext : AbpDbContext
2.第二種就是替換已經注冊的實體倉儲服務
回到上面問題,AbpEntityFrameworkCoreModule 模塊先注冊 ? 其實上面寫到了,在我們自定義的模塊注冊時,可以重新注冊倉儲服務
//Custom repository
Configuration.ReplaceService<IRepository<Post, Guid>>(() =>
{
IocManager.IocContainer.Register(
Component.For<IRepository<Post, Guid>, IPostRepository,
PostRepository>()
.ImplementedBy<PostRepository>()
.LifestyleTransient()
);
});
就是要必須在 AbpEntityFrameworkCoreModule 注冊之后,否則就會被覆蓋哦,這里也就呼應了上面的問題了
倉儲三要素:
- 倉儲的生命周期:倉儲都是臨時性的,需要的時候創建,用完銷毀。
- 數據庫的連接和管理倉儲的方法中,數據庫的連接和管理都是由ABP框架自動處理的。當方法被調用的時候,ABP自動開啟數據庫的連接同時開啟事務,當方法結束后,ABP會將實體數據保存,然后斷開連接。當在倉儲方法中調用倉儲方法的時候,此時只會創建一個數據庫連接,他們共同享用數據庫連接和事務,由最上層的那個倉儲方法進行管理。
- 倉儲的最佳實踐在ABP框架初始化的時候已經為每一個實體類都默認的實現了相應的倉儲,這些倉儲里的方法基本可以滿足日常的開發需求,所以不要自己手動創建倉儲