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<,>)
            )]

2.第二种就是替换已经注册的实体仓储服务

回到上面问题,AbpEntityFrameworkCoreModule 模块先注册 ? 其实上面写到了,在我们自定义的模块注册时,可以重新注册仓储服务

//Custom repository

            Configuration.ReplaceService<IRepository<Post, Guid>>(() =>
            {
                IocManager.IocContainer.Register(
                    Component.For<IRepository<Post, Guid>, IPostRepository,
PostRepository>()
                        .ImplementedBy<PostRepository>()
                        .LifestyleTransient()
                );
            });

就是要必须在 AbpEntityFrameworkCoreModule 注册之后,否则就会被覆盖哦,这里也就呼应了上面的问题了

仓储三要素:

  1. 仓储的生命周期:仓储都是临时性的,需要的时候创建,用完销毁。
  2. 数据库的连接和管理仓储的方法中,数据库的连接和管理都是由ABP框架自动处理的。当方法被调用的时候,ABP自动开启数据库的连接同时开启事务,当方法结束后,ABP会将实体数据保存,然后断开连接。当在仓储方法中调用仓储方法的时候,此时只会创建一个数据库连接,他们共同享用数据库连接和事务,由最上层的那个仓储方法进行管理。
  3. 仓储的最佳实践在ABP框架初始化的时候已经为每一个实体类都默认的实现了相应的仓储,这些仓储里的方法基本可以满足日常的开发需求,所以不要自己手动创建仓储

ABP 数据访问 - IRepository 仓储的更多相关文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先

    上一篇文章(https://www.cnblogs.com/meowv/p/12909558.html)完善了项目中的代码,接入了Swagger.本篇主要使用Entity Framework Core ...

  2. ABP框架之——数据访问基础架构

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享阅读心得,希望我的文章能成为你成长路上的一块垫脚石,我们一起精进. 几乎所有的业务应用程序都要适用一种数据库基础架构,用来实现数据访问逻辑,以便从数 ...

  3. 同时支持EF+Dapper的混合仓储,助你快速搭建数据访问层

    背景 17年开始,公司开始向DotNet Core转型,面对ORM工具的选型,当时围绕Dapper和EF发生了激烈的讨论.项目团队更加关注快速交付,他们主张使用EF这种能快速开发的ORM工具:而在线业 ...

  4. 项目架构开发:数据访问层之Repository

    接上文 项目架构开发:数据访问层之Logger 本章我们继续IRepository开发,这个仓储与领域模式里边的仓储有区别,更像一个工具类,也就是有些园友说的“伪仓储”, 这个仓储只实现单表的CURD ...

  5. ASP.NET Core模块化前后端分离快速开发框架介绍之3、数据访问模块介绍

    源码 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:https://nm.iamoldli.com 账户:admin 密码:admin 前 ...

  6. 数据访问层之Repository

    数据访问层之Repository   接上文 项目架构开发:数据访问层之Logger 本章我们继续IRepository开发,这个仓储与领域模式里边的仓储有区别,更像一个工具类,也就是有些园友说的“伪 ...

  7. ABP领域层定义仓储并实现

    原文作者:圣杰 原文地址:ABP入门系列(3)——领域层定义仓储并实现 在原文作者上进行改正,适配ABP新版本.内容相同 一.先来介绍下仓储 仓储(Repository): 仓储用来操作数据库进行数据 ...

  8. 数据访问模式之Repository模式

    数据访问模式之Repository模式   数据访问层无非就是对数据进行增删改查,其中增.删.改等我们可以抽象出来写一个公共的接口或抽象类来定义这些方法,并采用一个基类实现这些方法,这样该基类派生的子 ...

  9. 项目架构开发:数据访问层之UnitOfWork

    接上文 项目架构开发:数据访问层之IQuery 本章我们继续IUnitOfWork的开发,从之前的IRepository接口中就可以看出,我们并没有处理单元事务, 数据CUD每次都是立即执行的,这样有 ...

随机推荐

  1. Mysql安装(解压版)

    文章首推 刷网课请点击这里 刷二级请点击这里 论文查重请点击这里 WIFI破解详细教程 今日主题:Mysql安装(解压版) 环境 系统:windows10 版本:mysql5.7.29 安装过程 1. ...

  2. 介绍使用Cordova和Web Starter Kit开发Android

    介绍 如今,每个人都想制作移动应用程序,为什么不呢?世界上有更多的移动设备比任何其他用户设备.Android尤其流行,但是为什么不从一个众所周知的跨平台应用的基础开始呢?Android的开发显然比其他 ...

  3. 2. 在TCGA中找到并下载意向数据

    听说过别人用生信分析"空手套白狼"的故事吧想做吗好想学哦~ 或多或少都知道GEO和TCGA这些公共数据库吧!那么你知道怎么在数据库上找到意向数据,并且成功下载呢?这第一步要难倒一大 ...

  4. Java源码详解系列(十一)--Spring的使用和源码

    Spring 是一个一站式的 Java 框架,致力于提高我们项目开发的效率.通过 Spring,我们可以避免编写大量额外代码,更专注于我们的核心逻辑.目前,Spring 已经成为最受欢迎的 Java ...

  5. 感觉学java学到自己的瓶颈期了,各种框架乱七八糟,感觉好乱。该怎么办!?

    通常我们都会有这样的一个疑问! 解决办法 这时候,你需要的是分清条理,重整知识架构 GitHub开源社区有一个这样的项目,我觉得非常好,很适合Java有基础但是想进阶提升的人. 项目简介 本期介绍的开 ...

  6. 用 Java 实现的八种常用排序算法

    八种排序算法可以按照如图分类 交换排序 所谓交换,就是序列中任意两个元素进行比较,根据比较结果来交换各自在序列中的位置,以此达到排序的目的. 1. 冒泡排序 冒泡排序是一种简单的交换排序算法,以升序排 ...

  7. git pull设置为无需密码

    https方式每次都要输入密码,按照如下设置即可输入一次就不用再手输入密码的困扰而且又享受https带来的极速 设置记住密码(默认15分钟): git config --global credenti ...

  8. Fiddler抓包工具 请求图标为一个锁的图标的设置

    第一步,Fiddler抓包的数据 前面的都是一个锁的图标,的设置方法, 然后 点击打开 按此设置图一 在图二, 图三. 其他默认就好

  9. Linux系统安装JDK1.8

    2020最新Linux系统发行版ContOS7演示安装JDK. 为防止操作权限不足,建议切换root用户,当然如果你对Linux命令熟悉,能够自主完成权限更新操作,可以不考虑此推荐. 更多命令学习推荐 ...

  10. 多测师讲解selenium _滚动条定位_高级讲师肖sir

    from selenium import webdriverfrom time import sleepdrvier=webdriver.Chrome()drvier.get('http://www. ...