• 基于ABP的AppUser对象扩展


      在ABP中AppUser表的数据字段是有限的,现在有个场景是和小程序对接,需要在AppUser表中添加一个OpenId字段。今天有个小伙伴在群中遇到的问题是基于ABP的AppUser对象扩展后,用户查询是没有问题的,但是增加和更新就会报"XXX field is required"的问题。本文以AppUser表扩展OpenId字段为例进行介绍。

    一.AppUser实体表

    AppUser.cs位于BaseService.Domain项目中,如下:

    public class AppUser : FullAuditedAggregateRoot, IUser
    {
        public virtual Guid? TenantId { get; private set; }
        public virtual string UserName { get; private set; }
        public virtual string Name { get; private set; }
        public virtual string Surname { get; private set; }
        public virtual string Email { get; private set; }
        public virtual bool EmailConfirmed { get; private set; }
        public virtual string PhoneNumber { get; private set; }
        public virtual bool PhoneNumberConfirmed { get; private set; }
    
        // 微信应用唯一标识
        public string OpenId { get; set; }
    
        private AppUser()
        {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    因为AppUser继承自聚合根,而聚合根默认都实现了IHasExtraProperties接口,否则如果想对实体进行扩展,那么需要实体实现IHasExtraProperties接口才行。

    二.实体扩展管理

    BaseEfCoreEntityExtensionMappings.cs位于BaseService.EntityFrameworkCore项目中,如下:

    public class BaseEfCoreEntityExtensionMappings
    {
        private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
    
        public static void Configure()
        {
            BaseServiceModuleExtensionConfigurator.Configure();
    
            OneTimeRunner.Run(() =>
            {
                ObjectExtensionManager.Instance
                    .MapEfCoreProperty(nameof(AppUser.OpenId), (entityBuilder, propertyBuilder) =>
                        {
                            propertyBuilder.HasMaxLength(128);
                            propertyBuilder.HasDefaultValue("");
                            propertyBuilder.IsRequired();
                        }
                    );
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    三.数据库上下文

    BaseServiceDbContext.cs位于BaseService.EntityFrameworkCore项目中,如下:

    [ConnectionStringName("Default")]
    public class BaseServiceDbContext : AbpDbContext
    {
        ......
        
        public BaseServiceDbContext(DbContextOptions options): base(options)
        {
        }
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
    
            builder.Entity(b =>
            {
                // AbpUsers和IdentityUser共享相同的表
                b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); 
                
                b.ConfigureByConvention();
                b.ConfigureAbpUser();
                
                b.Property(x => x.OpenId).HasMaxLength(128).HasDefaultValue("").IsRequired().HasColumnName(nameof(AppUser.OpenId));
            });
    
            builder.ConfigureBaseService();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    四.数据库迁移和更新

    1.数据库迁移

    dotnet ef migrations add add_appuser_openid
    
    • 1

    2.数据库更新

    dotnet ef database update
    
    • 1

    3.对额外属性操作

    数据库迁移和更新后,在AbpUsers数据库中就会多出来一个OpenId字段,然后在后端中就可以通过SetProperty或者GetProperty来操作额外属性了:

    // 设置额外属性
    var user = await _identityUserRepository.GetAsync(userId);
    user.SetProperty("Title", "My custom title value!");
    await _identityUserRepository.UpdateAsync(user);
    
    // 获取额外属性
    var user = await _identityUserRepository.GetAsync(userId);
    return user.GetProperty("Title");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    但是在前端呢,主要是通过ExtraProperties字段这个json类型来操作额外属性的。

    五.应用层增改操作

    UserAppService.cs位于BaseService.Application项目中,如下:

    1.增加操作

    [Authorize(IdentityPermissions.Users.Create)]
    public async Task Create(BaseIdentityUserCreateDto input)
    {
        var user = new IdentityUser(
            GuidGenerator.Create(),
            input.UserName,
            input.Email,
            CurrentTenant.Id
        );
    
        input.MapExtraPropertiesTo(user);
    
        (await UserManager.CreateAsync(user, input.Password)).CheckErrors();
        await UpdateUserByInput(user, input);
    
        var dto = ObjectMapper.Map(user);
    
        foreach (var id in input.JobIds)
        {
            await _userJobsRepository.InsertAsync(new UserJob(CurrentTenant.Id, user.Id, id));
        }
    
        foreach (var id in input.OrganizationIds)
        {
            await _userOrgsRepository.InsertAsync(new UserOrganization(CurrentTenant.Id, user.Id, id));
        }
    
        await CurrentUnitOfWork.SaveChangesAsync();
    
        return dto;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.更新操作

    [Authorize(IdentityPermissions.Users.Update)]
    public async Task UpdateAsync(Guid id, BaseIdentityUserUpdateDto input)
    {
        UserManager.UserValidators.Clear();
        
        var user = await UserManager.GetByIdAsync(id);
        user.ConcurrencyStamp = input.ConcurrencyStamp;
    
        (await UserManager.SetUserNameAsync(user, input.UserName)).CheckErrors();
    
        await UpdateUserByInput(user, input);
        input.MapExtraPropertiesTo(user);
    
        (await UserManager.UpdateAsync(user)).CheckErrors();
    
        if (!input.Password.IsNullOrEmpty())
        {
            (await UserManager.RemovePasswordAsync(user)).CheckErrors();
            (await UserManager.AddPasswordAsync(user, input.Password)).CheckErrors();
        }
    
        var dto = ObjectMapper.Map(user);
        dto.SetProperty("OpenId", input.ExtraProperties["OpenId"]);
        
        await _userJobsRepository.DeleteAsync(_ => _.UserId == id);
    
        if (input.JobIds != null)
        {
            foreach (var jid in input.JobIds)
            {
                await _userJobsRepository.InsertAsync(new UserJob(CurrentTenant.Id, id, jid));
            }
        }
    
        await _userOrgsRepository.DeleteAsync(_ => _.UserId == id);
    
        if (input.OrganizationIds != null)
        {
            foreach (var oid in input.OrganizationIds)
            {
                await _userOrgsRepository.InsertAsync(new UserOrganization(CurrentTenant.Id, id, oid));
            }
        }
    
        await CurrentUnitOfWork.SaveChangesAsync();
    
        return dto;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    3.UpdateUserByInput()函数

    上述增加和更新操作代码中用到的UpdateUserByInput()函数如下:

    protected virtual async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateDtoBase input)
    {
        if (!string.Equals(user.Email, input.Email, StringComparison.InvariantCultureIgnoreCase))
        {
            (await UserManager.SetEmailAsync(user, input.Email)).CheckErrors();
        }
    
        if (!string.Equals(user.PhoneNumber, input.PhoneNumber, StringComparison.InvariantCultureIgnoreCase))
        {
            (await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
        }
    
        (await UserManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)).CheckErrors();
    
        user.Name = input.Name;
        user.Surname = input.Surname;
        
        user.SetProperty("OpenId", input.ExtraProperties["OpenId"]);
        
        if (input.RoleNames != null)
        {
            (await UserManager.SetRolesAsync(user, input.RoleNames)).CheckErrors();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

      实体扩展的好处是不用继承实体,或者修改实体就可以对实体进行扩展,可以说是非常的灵活,但是实体扩展并不适用于复杂的场景,比如使用额外属性创建索引和外键、使用额外属性编写SQL或LINQ等。遇到这种情况该怎么办呢?有种方法是直接引用源码和添加字段。

    参考文献:
    [1]自定义应用模块:https://docs.abp.io/zh-Hans/abp/6.0/Customizing-Application-Modules-Guide
    [2]自定义应用模块-扩展实体:https://docs.abp.io/zh-Hans/abp/6.0/Customizing-Application-Modules-Extending-Entities
    [3]自定义应用模块-重写服务:https://docs.abp.io/zh-Hans/abp/6.0/Customizing-Application-Modules-Overriding-Services
    [4]ABP-MicroService:https://github.com/WilliamXu96/ABP-MicroService

  • 相关阅读:
    致敬!百里煤海战斗在第二战线上的人们
    FPGA实现UDP传输视频,提供2套verilog工程源码和接收显示上位机程序
    VPN的不同种类及现网应用场景
    CTF--Web安全--SQL注入之‘绕过方法’
    docker基本使用总结
    Shiro安全(二):Shiro-550内存马注入
    【计算机网络】ip协议
    揭秘 Nacos 的 AP 架构 「Distro 一致性协议」(一)
    [iOS]-NSOperation、NSOperationQueue
    第18章_MySQL8新特性之CTE(公用表表达式)
  • 原文地址:https://blog.csdn.net/shengshengwang/article/details/126256187