在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>();
        }
    }

這樣就可以順利轉換獲得對應的信息。

 


免責聲明!

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



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