在 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/a…
[2] 自定义利用模块 - 扩大实体:https://docs.abp.io/zh-Hans/a…
[3] 自定义利用模块 - 重写服务:https://docs.abp.io/zh-Hans/a…
[4]ABP-MicroService:https://github.com/WilliamXu9…
本文由 mdnice 多平台公布