2

基于ABP的AppUser对象扩展 - 阿升1990

 2 years ago
source link: https://www.cnblogs.com/shengshengwang/p/16567692.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中AppUser表的数据字段是有限的,现在有个场景是和小程序对接,需要在AppUser表中添加一个OpenId字段。今天有个小伙伴在群中遇到的问题是基于ABP的AppUser对象扩展后,用户查询是没有问题的,但是增加和更新就会报"XXX field is required"的问题。本文以AppUser表扩展OpenId字段为例进行介绍。

一.AppUser实体表

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

public class AppUser : FullAuditedAggregateRoot<Guid>, 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()
    {
    }
}

因为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<IdentityUser, string>(nameof(AppUser.OpenId), (entityBuilder, propertyBuilder) =>
                    {
                        propertyBuilder.HasMaxLength(128);
                        propertyBuilder.HasDefaultValue("");
                        propertyBuilder.IsRequired();
                    }
                );
        });
    }
}

三.数据库上下文

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

[ConnectionStringName("Default")]
public class BaseServiceDbContext : AbpDbContext<BaseServiceDbContext>
{
    ......
    
    public BaseServiceDbContext(DbContextOptions<BaseServiceDbContext> options): base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<AppUser>(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.数据库迁移

dotnet ef migrations add add_appuser_openid

2.数据库更新

dotnet ef database update

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<string>("Title");

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

五.应用层增改操作

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

1.增加操作

[Authorize(IdentityPermissions.Users.Create)]
public async Task<IdentityUserDto> 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<IdentityUser, IdentityUserDto>(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;
}

2.更新操作

[Authorize(IdentityPermissions.Users.Update)]
public async Task<IdentityUserDto> 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<IdentityUser, IdentityUserDto>(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;
}

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

  实体扩展的好处是不用继承实体,或者修改实体就可以对实体进行扩展,可以说是非常的灵活,但是实体扩展并不适用于复杂的场景,比如使用额外属性创建索引和外键、使用额外属性编写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


Recommend

  • 7

      首先,标准化的英文是Standardization,归一化的英文是Normalization,正则化的英文是Regularization。标准化是特征缩放的一种方式,需要注意的是标准化之后的数据分布并不一定是正态分布,因为标准化并不会改变原始数据的分布。归一化的目的是消除不同量纲及单位...

  • 3

      由于软件系统中可能有着不同的数据库,不同的ORM,仓储思想的本质是解耦它们。在ABP中具体的实现仓储接口定义在领域层,实现在基础设施层。仓储接口被领域层(比如领域服务)和应用层用来访问数据库,操作聚合根,聚合根就是业务单元。这篇文章主要分析怎么通过规约...

  • 5

      本文主要介绍了通过构造函数和领域服务创建实体2种方式,后者多用于在创建实体时需要其它业务规则检测的场景。最后介绍了在应用服务层中如何进行实体的更新操作。 一.通过构造函数创建实体 假如Issue的聚合根类为:

  • 5

      本文主要介绍了多应用层的问题,包括原因和实现。通过理解介绍了如何区分领域逻辑和应用逻辑,哪些是正确的实践,哪些是不推荐的或者错误的实践。 一.多应用层的问题 1.多应用层介绍   不知道...

  • 4
    • www.cnblogs.com 2 years ago
    • Cache

    ML.NET相关资源整理 - 阿升1990

      在人工智能领域,无论是机器学习,还是深度学习等,Python编程语言都是绝对的主流,尽管底层都是C++实现的,似乎人工智能和C#/F#编程语言没什么关系。在人工智能的工程实现,通常都是将Python训练好的人工智能模型封装为REST API,以供其它的系统调用。虽然C#也确...

  • 5

      本文介绍了CCA解决的问题,CCA原理的推导过程,以及对计算结果物理意义的解释。并且通过SPSS和R操作演示了一个关于CCA的例子。数据文件下载参考[8],SPSS输出结果文件下载参考[9],R代码文件下载参考[10]。 一.CCA工...

  • 2
    • www.cnblogs.com 2 years ago
    • Cache

    ABP 6.0.0-rc.1的新特性 - 阿升1990

      2022-07-26官方发布ABP 6.0.0-rc.1版本,本文挑选了几个新特性进行了介绍,主要包括LeptonX Lite默认主题、OpenIddict模块,以及如何将Identity Server迁移到OpenIddict。据ABP官方公众号介绍,ABP 6.0.0稳定版的计划发布日期为2022-09-06,具体以实际发布日期为准...

  • 8
    • www.cnblogs.com 2 years ago
    • Cache

    ABP中的数据过滤器 - 阿升1990

      本文首先介绍了ABP内置的软删除过滤器(ISoftDelete)和多租户过滤器(IMultiTenant),然后介绍了如何实现一个自定义过滤器,最后介绍了在软件开发过程中遇到的实际问题,同时给出了解决问题的一个未必最优的思路。 一.预定义过滤器...

  • 14
    • www.cnblogs.com 2 years ago
    • Cache

    PyTorch中的CUDA操作 - 阿升1990

    PyTorch中的CUDA操作   CUDA(Compute Unified Device Architecture)是NVIDIA推出的异构计算平台,PyTorch中有专门的模块tor...

  • 7

    SPSS统计教程:卡方检验 本文简要的介绍了卡方分布、卡方概率密度函数和卡方检验,并通过SPSS实现了一个卡方检验例子,不...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK