ABP 數據訪問 - IRepository 倉儲


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<Entity,PrimaryKey>用來重寫主鍵
那是通過什么類來實現的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 注冊之后,否則就會被覆蓋哦,這里也就呼應了上面的問題了

倉儲三要素:

  1. 倉儲的生命周期:倉儲都是臨時性的,需要的時候創建,用完銷毀。
  1. 數據庫的連接和管理倉儲的方法中,數據庫的連接和管理都是由ABP框架自動處理的。當方法被調用的時候,ABP自動開啟數據庫的連接同時開啟事務,當方法結束后,ABP會將實體數據保存,然后斷開連接。當在倉儲方法中調用倉儲方法的時候,此時只會創建一個數據庫連接,他們共同享用數據庫連接和事務,由最上層的那個倉儲方法進行管理。
  2. 倉儲的最佳實踐在ABP框架初始化的時候已經為每一個實體類都默認的實現了相應的倉儲,這些倉儲里的方法基本可以滿足日常的開發需求,所以不要自己手動創建倉儲


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM