??在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