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


    前面介绍了一些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>();
            }
        }

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

  • 相关阅读:
    构造方法中使用this的含义
    Android Bundle类
    Android中使用PULL方式解析XML文件
    Android 创建与解析XML(四)—— Pull方式
    File的getPath()和getAbsolutePath()和getCanonicalPath()的区别
    Android-取出SDcard卡下指定后缀名的文件
    page、request、session和application有什么区别?
    prepareStatement的用法和解释
    pageContext对象的用法
    使用JSP连接MySql数据库读取HTML表单数据进行存贮
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/15908131.html
Copyright © 2020-2023  润新知