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

}
publicAppUser(IUserData user)
:
base(user.Id)
{
TenantId
=user.TenantId;
UpdateInternal(user);
}
public virtual boolUpdate(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 boolEquals(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 voidUpdateInternal(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
{
publicAppUserLookupService(
IAppUserRepository userRepository,
IUnitOfWorkManager unitOfWorkManager)
:
base(
userRepository,
unitOfWorkManager)
{

}
protected overrideAppUser CreateUser(IUserData externalUser)
{
return newAppUser(externalUser);
}
}

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

2、领域对象的关系处理

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

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

    /// <summary>
    ///岗位用户中间表对象,领域对象/// </summary>
    [Table("TB_JobPostUser")]public classJobPostUser : CreationAuditedEntity, IMultiTenant
{
/// <summary> ///默认构造函数(需要初始化属性的在此处理)/// </summary> publicJobPostUser()
{
}
/// <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 classFrameworkDbContextModelBuilderExtensions
{
public static voidConfigureFramework(
[NotNull]
thisModelBuilder 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 classJobPostMapProfile : Profile  
{
publicJobPostMapProfile()
{
CreateMap
<JobPostDto, JobPost>();
CreateMap
<JobPost, JobPostDto>().Ignore(x => x.Users); //忽略Users,否则类型不对出错 CreateMap<CreateJobPostDto, JobPost>();
}
}

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

标签: none

添加新评论