8

在ABP VNext框架中处理和用户相关的多对多的关系

 2 years ago
source link: https://www.cnblogs.com/wuhuacong/p/15908131.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面介绍了一些ABP VNext架构上的内容,随着内容的细化,我们会发现ABP VNext框架中的Entity Framework处理表之间的引用关系还是比较麻烦的,一不小心就容易出错了,本篇随笔介绍在ABP VNext框架中处理和用户相关的多对多的关系处理。

我们这里需要在一个基础模块中创建一个岗位管理,岗位需要包含一些用户,和用户是多对多的关系,因此需要创建一个中间表来放置他们的关系,如下所示的数据库设计。

 这个是典型的多对多关系的处理,我们来看看如何在在ABP VNext框架中处理这个关系。

1、扩展系统用户信息

为了模块间不产生依赖,例如用户表,迁移dbcontext中使用了IdentityUser,而运行的dbcontext使用了appuser进行了对其的映射,https://github.com/abpframework/abp/issues/1998 

因此参照实例模块Bloging(https://github.com/abpframework/abp/tree/dev/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users)中的BlogUser来扩展一下模块的用户对象

   public class AppUser : AggregateRoot<Guid>, IUser, IUpdateUserData
    {
        public virtual Guid? TenantId { get; protected set; }

        public virtual string UserName { get; protected set; }

        public virtual string Email { get; protected set; }

        public virtual string Name { get; set; }

        public virtual string Surname { get; set; }

        public virtual bool EmailConfirmed { get; protected set; }

        public virtual string PhoneNumber { get; protected set; }

        public virtual bool PhoneNumberConfirmed { get; protected set; }

        protected AppUser()
        {

        }

        public AppUser(IUserData user)
            : base(user.Id)
        {
            TenantId = user.TenantId;
            UpdateInternal(user);
        }

        public virtual bool Update(IUserData user)
        {
            if (Id != user.Id)
            {
                throw new ArgumentException($"Given User's Id '{user.Id}' does not match to this User's Id '{Id}'");
            }

            if (TenantId != user.TenantId)
            {
                throw new ArgumentException($"Given User's TenantId '{user.TenantId}' does not match to this User's TenantId '{TenantId}'");
            }

            if (Equals(user))
            {
                return false;
            }

            UpdateInternal(user);
            return true;
        }

        protected virtual bool Equals(IUserData user)
        {
            return Id == user.Id &&
                   TenantId == user.TenantId &&
                   UserName == user.UserName &&
                   Name == user.Name &&
                   Surname == user.Surname &&
                   Email == user.Email &&
                   EmailConfirmed == user.EmailConfirmed &&
                   PhoneNumber == user.PhoneNumber &&
                   PhoneNumberConfirmed == user.PhoneNumberConfirmed;
        }

        protected virtual void UpdateInternal(IUserData user)
        {
            Email = user.Email;
            Name = user.Name;
            Surname = user.Surname;
            EmailConfirmed = user.EmailConfirmed;
            PhoneNumber = user.PhoneNumber;
            PhoneNumberConfirmed = user.PhoneNumberConfirmed;
            UserName = user.UserName;
        }
    }

另外我们还需要参照创建一个AppUserLookupService来快捷获取用户的对象信息。只需要继承自UserLookupService即可,如下代码所示,放在领域层中。

    public class AppUserLookupService : UserLookupService<AppUser, IAppUserRepository>, IAppUserLookupService
    {
        public AppUserLookupService(
            IAppUserRepository userRepository,
            IUnitOfWorkManager unitOfWorkManager)
            : base(
                userRepository,
                unitOfWorkManager)
        {

        }

        protected override AppUser CreateUser(IUserData externalUser)
        {
            return new AppUser(externalUser);
        }
    }

这样就可以在需要的时候(一般在AppService应用服务层中注入IAppUserLookupService),可以利用这个接口获取对应的用户信息,来实现相关的用户关联操作。

2、领域对象的关系处理

在常规的岗位领域对象中,增加一个和中间表的关系信息。

 这个中间表的领域对象如下所示。

    /// <summary>
    /// 岗位用户中间表对象,领域对象
    /// </summary>
    [Table("TB_JobPostUser")]
    public class JobPostUser : CreationAuditedEntity, IMultiTenant
    { 
        /// <summary>
        /// 默认构造函数(需要初始化属性的在此处理)
        /// </summary>
        public JobPostUser()
        {
        }

        /// <summary>
        /// 参数化构造函数
        /// </summary>
        /// <param name="postId"></param>
        /// <param name="userId"></param>
        /// <param name="tenantId"></param>
        public JobPostUser(string postId, Guid userId, Guid? tenantId = null)
        {
            PostId = postId;
            UserId = userId;
            TenantId = tenantId;
        }

        /// <summary>
        /// 复合键的处理
        /// </summary>
        /// <returns></returns>
        public override object[] GetKeys()
        {
            return new object[] { PostId, UserId };
        }

        #region Property Members

        [Required]
        public virtual string PostId { get; set; }

        [Required]
        public virtual Guid UserId { get; set; }

        /// <summary>
        /// 租户ID
        /// </summary>
        public virtual Guid? TenantId { get; protected set; }

        #endregion
    }

这里主要就是注意复合键的处理,其他的都是代码自动生成的(利用代码生成工具Database2Sharp

然后在EntityFramework项目中处理它们之间的关系,如下代码所示

    public static class FrameworkDbContextModelBuilderExtensions
    {
        public static void ConfigureFramework(
            [NotNull] this ModelBuilder builder)
        {
            Check.NotNull(builder, nameof(builder));

            builder.Entity<JobPost>(b =>
            {
                b.ConfigureByConvention();
                b.HasMany(x => x.Users).WithOne().HasForeignKey(jp => jp.PostId);
                b.ApplyObjectExtensionMappings();
            });
            builder.Entity<JobPostUser>(b =>
            {
                b.ConfigureByConvention();

                b.HasKey(pu => new { pu.PostId, pu.UserId });
                b.HasIndex(pu => new { pu.PostId, pu.UserId });

                b.ApplyObjectExtensionMappings();
            });

            builder.TryConfigureObjectExtensions<FrameworkDbContext>();
        }
    }

通过JobPost关系中的HasForeignKey(jp => jp.PostId),建立它们的外键关系,通过JobPostUser关系中 b.HasKey(pu => new { pu.PostId, pu.UserId });创建中间表的复合键关系。

默认在获取实体类的时候,关联信息是没有加载的,我们可以通过设置的方式实现预先加载或者懒加载处理,如下是通过设置,可以设置JobPost中加载用户信息。

 不过不是所有的实体信息,都是要设置这样,否则有性能问题的。

最后测试的时候,可以看到返回的JobPost领域对象中附带有用户相关的信息,如下截图所示。

这样我们就可以通过该对象获取用户的相关信息,来进行相关的处理。

 我们领域对象JobPost里面有Users属性,它是一个中间表的信息,

 而我们在Dto层,一般直接面向的是用户信息,那么JobPostDto的信息定义如下所示。

 那么我们在映射的时候,需要注意他们类型不一致的问题,需要忽略它的这个属性的映射。

    /// <summary>
    /// JobPost,映射文件
    /// 注:一个业务对象拆分为一个映射文件,方便管理。
    /// </summary>
    public class JobPostMapProfile : Profile  
    {
        public JobPostMapProfile()
        {
            CreateMap<JobPostDto, JobPost>();
            CreateMap<JobPost, JobPostDto>().Ignore(x => x.Users); //忽略Users,否则类型不对出错
            CreateMap<CreateJobPostDto, JobPost>();
        }
    }

这样就可以顺利转换获得对应的信息。

主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
  转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com 

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK