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

一.AppUser实体表

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

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

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

二.实体扩展管理

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

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

三.数据库上下文

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

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

四.数据库迁移和更新

1.数据库迁移

  1. dotnet ef migrations add add_appuser_openid

2.数据库更新

  1. dotnet ef database update

3.对额外属性操作

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

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

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

五.应用层增改操作

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

1.增加操作

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

2.更新操作

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

3.UpdateUserByInput()函数

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

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

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

基于ABP的AppUser对象扩展的更多相关文章

  1. ABP(现代ASP.NET样板开发框架)系列之16、ABP应用层——数据传输对象(DTOs)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Project ...

  2. ABP应用层——数据传输对象(DTOs)

    ABP应用层——数据传输对象(DTOs) 基于DDD的现代ASP.NET开发框架--ABP系列之16.ABP应用层——数据传输对象(DTOs) ABP是“ASP.NET Boilerplate Pro ...

  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)

    上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操作做一个提醒.当每次抓取完数据后, ...

  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  6. 循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

    在我们一般系统中,往往都会涉及到附件的处理,有时候附件是图片文件,有时候是Excel.Word等文件,一般也就是可以分为图片附件和其他附件了,图片附件可以进行裁剪管理.多个图片上传管理,及图片预览操作 ...

  7. 基于ABP落地领域驱动设计-01.全景图

    什么是领域驱动设计? 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与不断发展的模型联系起来,专注于核心领域逻辑,而不是基础设施细节.DDD适用于复杂领域和大规模应用,而不是 ...

  8. 基于ABP落地领域驱动设计-02.聚合和聚合根的最佳实践和原则

    目录 前言 聚合 聚合和聚合根原则 包含业务原则 单个单元原则 事务边界原则 可序列化原则 聚合和聚合根最佳实践 只通过ID引用其他聚合 用于 EF Core 和 关系型数据库 保持聚合根足够小 聚合 ...

  9. 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则

    目录 系列文章 仓储 仓储的通用原则 仓储中不包含领域逻辑 规约 在实体中使用规约 在仓储中使用规约 组合规约 学习帮助 围绕DDD和ABP Framework两个核心技术,后面还会陆续发布核心构件实 ...

随机推荐

  1. HDFS 细粒度锁优化,FusionInsight MRS有妙招

    摘要:华为云FusionInsight MRS通过FGL对HDFS NameNode锁机制进行优化,有效提升了NameNode的读写吞吐量,从而能够支持更多数据,更多业务请求访问,从而更好的支撑政企客 ...

  2. MTK 虚拟 sensor bring up (pick up) sensor1.0

    pick up bring up sensor1.0 1.pick up对比 2.SCP 1.添加驱动文件 2.添加编译环境(打开开关) 注:编译过程中如果显示内存不够 3.修改底层数据上报方式 3. ...

  3. Java - 四种引用类型及应用场景

    1. 强引用 new 一个对象的时候,就是强引用 Object object = new Object(); 只要强引用存在,垃圾回收就不会回收该对象,内存不足时会抛出OOM. 2. 软引用 定义:非 ...

  4. MySQL数据库4

    内容概要 查询关键字 查询关键字之having过滤 查询关键字之distinct去重 查询关键字之order by排序 查询关键字之limit分页 查询关键字之regexp正则 多表查询思路 可视化软 ...

  5. Python数据分析--Numpy常用函数介绍(6)--Numpy中矩阵和通用函数

    在NumPy中,矩阵是 ndarray 的子类,与数学概念中的矩阵一样,NumPy中的矩阵也是二维的,可以使用 mat . matrix 以及 bmat 函数来创建矩阵. 一.创建矩阵 mat 函数创 ...

  6. Ubuntu,CenOS等Linux系统更改环境变量方法,以安装anaconda为例

    [环境配置的原因] 在windows系统下,很多软件的安装都需要设置环境变量,比如安装JAVA JDK.如果不安装环境变量,在非软件安装的目录下运行javac命令,将会报告"找不到文件&qu ...

  7. Java基础-JVM篇

    1.1 .线程 ​ 这里所说的线程指程序执行过程中的一个线程实体.JVM 允许一个应用并发执行多个线程.Hotspot JVM 中的 Java 线程与原生操作系统线程有直接的映射关系.当线程本地存储. ...

  8. 我大抵是卷上瘾了,横竖睡不着!竟让一个Bug,搞我两次!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言:一个Bug 没想到一个Bug,竟然搞我两次! 我大抵是卷上瘾了,横竖都睡不着,坐起来 ...

  9. Bitbucket 使用 SSH 拉取仓库失败的问题

    问题 在 Bitbucket 使用 Linux 机器上 ssh-keygen 工具生成的公钥作为 API KEY,然后在 Jenkins 里面存储对应的 SSH 私钥,最后执行 Job 的时候,Win ...

  10. Wabacus框架中inputbox和datepicker实现时间日历

    前提是要引入WdatePicker.js. 一.年月日时分秒(中文) <inputbox type="datepicker" inputboxparams="dat ...