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框架初始化的時候已經為每一個實體類都預設的實現了相應的倉儲,這些倉儲裡的方法基本可以滿足日常的開發需求,所以不要自己手動建立倉儲