说明

Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲

缘起

本篇文章缘起于dyAbp大佬们在给夏琳儿(简称:小富婆)讲解技术的时候发起,因为多用户设计和用户扩展属性设计在社区已经是一个每天都会有人来问一遍的问题,这里浅谈一下我的理解,源码是根据EasyAbp作者Super写的代码,根据我自己的理解去分析的想法。

扩展属性

先从我们单用户系统来讲,如果我该如何扩展用户属性?

在Abp默认解决方案Domain.Shared中更改ConfigureExtraProperties,该操作会向IdentityUser实体添加SocialSecurityNumber属性

public static void ConfigureExtraProperties()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>( //property type: string
"SocialSecurityNumber", //property name
property =>
{
//validation rules
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(
new StringLengthAttribute(64) {
MinimumLength = 4
}
); //...other configurations for this property
}
);
});
});
});
}

EntityExtensions还提供了很多配置操作,这里就简单的举几个常用的例子更多详细操作可以在文章下方连接到官方连接。

// 默认值选项
property =>
{
property.DefaultValue = 42;
}
//默认值工厂选项
property =>
{
property.DefaultValueFactory = () => DateTime.Now;
}
// 数据注解属性
property =>
{
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4});
}
//验证操作
property =>
{
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); property.Validators.Add(context =>
{
if (((string) context.Value).StartsWith("B"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Social security number can not start with the letter 'B', sorry!",
new[] {"extraProperties.SocialSecurityNumber"}
)
);
}
}); }

目前这种配置方式如果你的前端是mvc或者razor pages是不需要改动代码的,页面会动态生成字段,但是如果是angular就需要人工来操作了,除了扩展属性外,你可能还需要部分或完全覆盖某些服务和页面组件才行,不过Abp官方文档都有相应的操作指南所以没有任何问题。

具体更多操作官方地址:https://docs.abp.io/en/abp/latest/Module-Entity-Extensions

另外就是大家最关系的数据存储问题,默认我们添加的数据都会在ExtraProperties以JSON对象方式进行存储

但如果你想用字段的方式进行存储的话,可以在你的.EntityFrameworkCore项目的类中写下这个。然后您需要使用标准Add-Migration和Update-Database命令来创建新的数据库迁移并将更改应用到您的数据库。

ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
);

多用户设计

举例你要开发学生管理系统

  • 老师和学生都会进入系统来做自己对应的操作,我们如何来隔离呢?

首先我们就可以想到通过角色来做权限分配做能力隔离

  • 然后学生和老师的参数不一样,怎么办,老师要填写工号、系部、教学科目、工龄,学生要填写年度、班级、学号?,看到过比较粗暴的方案就是直接在IdentityUser表全给干上去,但是这种做法相对于某个角色来看是不是太冗余?

这里我参考Super的一个做法采用使用自己的数据库表/集合创建新实体,具体什么意思呢?

我们创建Teacher实体,该实体通过UserId指定IdentityUser,来存储作为老师的额外属性

public class Teacher : AggregateRoot<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; } public virtual Guid UserId { get; protected set; } public virtual bool Active { get; protected set; } [NotNull]
public virtual string Name { get; protected set; } public virtual int? Age { get; protected set; } protected Teacher()
{
} public Teacher(Guid id, Guid? tenantId, Guid userId, bool active, [NotNull] string name, int? age) : base(id)
{
TenantId = tenantId;
UserId = userId; Update(active, name, age);
} public void Update(bool active, [NotNull] string name, int? age)
{
Active = active;
Name = name;
Age = age;
}
}

处理方案是通过订阅UserEto,这是User预定义的专用事件类,当User产生Created、Updated和Deleted操作收会到通知,然后执行我们自己逻辑,

 [UnitOfWork]
public class TeacherUserInfoSynchronizer :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
IDistributedEventHandler<EntityUpdatedEto<UserEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>,
ITransientDependency
{
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly IUserRoleFinder _userRoleFinder;
private readonly IRepository<Teacher, Guid> _teacherRepository; public TeacherUserInfoSynchronizer(
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
IUserRoleFinder userRoleFinder,
IRepository<Teacher, Guid> teacherRepository)
{
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_userRoleFinder = userRoleFinder;
_teacherRepository = teacherRepository;
} public async Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
if (!await HasTeacherRoleAsync(eventData.Entity))
{
return;
} await CreateOrUpdateTeacherAsync(eventData.Entity, true);
} public async Task HandleEventAsync(EntityUpdatedEto<UserEto> eventData)
{
if (await HasTeacherRoleAsync(eventData.Entity))
{
await CreateOrUpdateTeacherAsync(eventData.Entity, true);
}
else
{
await CreateOrUpdateTeacherAsync(eventData.Entity, false);
}
} public async Task HandleEventAsync(EntityDeletedEto<UserEto> eventData)
{
await TryUpdateAndDeactivateTeacherAsync(eventData.Entity);
} protected async Task<bool> HasTeacherRoleAsync(UserEto user)
{
var roles = await _userRoleFinder.GetRolesAsync(user.Id); return roles.Contains(MySchoolConsts.TeacherRoleName);
} protected async Task CreateOrUpdateTeacherAsync(UserEto user, bool active)
{
var teacher = await FindTeacherAsync(user); if (teacher == null)
{
teacher = new Teacher(_guidGenerator.Create(), _currentTenant.Id, user.Id, active, user.Name, null); await _teacherRepository.InsertAsync(teacher, true);
}
else
{
teacher.Update(active, user.Name, teacher.Age); await _teacherRepository.UpdateAsync(teacher, true);
}
} protected async Task TryUpdateAndDeactivateTeacherAsync(UserEto user)
{
var teacher = await FindTeacherAsync(user); if (teacher == null)
{
return;
} teacher.Update(false, user.Name, teacher.Age); await _teacherRepository.UpdateAsync(teacher, true);
} protected async Task<Teacher> FindTeacherAsync(UserEto user)
{
return await _teacherRepository.FindAsync(x => x.UserId == user.Id);
}
}

结语

我也是在阅读文档和对照Super大佬的代码后自己的理解,文中可能某些地方可能与作者设计有差距,还请大家多多理解!

也欢迎大家阅读我的Abp vNext系列教程

联系作者:加群:867095512 @MrChuJiu

Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计的更多相关文章

  1. 番外篇--Moddule Zero多租户管理

    番外篇--Moddule Zero多租户管理 2.1.1 关于多租户 强烈建议阅读这个文件前阅读多租户文档. 2.1.2 启用多租户 ASP.NET Boilerplate和module-zero可以 ...

  2. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  3. iOS冰与火之歌(番外篇) - 基于PEGASUS(Trident三叉戟)的OS X 10.11.6本地提权

    iOS冰与火之歌(番外篇) 基于PEGASUS(Trident三叉戟)的OS X 10.11.6本地提权 蒸米@阿里移动安全 0x00 序 这段时间最火的漏洞当属阿联酋的人权活动人士被apt攻击所使用 ...

  4. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...

  5. 可视化(番外篇)——在Eclipse RCP中玩转OpenGL

    最近在看有关Eclipse RCP方面的东西,鉴于Gephi是使用opengl作为绘图引擎,所以,萌生了在Eclipse RCP下添加画布,使用opengl绘图的想法,网上有博文详细介绍这方面的内容, ...

  6. 可视化(番外篇)——SWT总结

    本篇主要介绍如何在SWT下构建一个应用,如何安装SWT Designer并破解已进行SWT的可视化编程,Display以及Shell为何物.有何用,SWT中的常用组件.面板容器以及事件模型等. 1.可 ...

  7. 【重走Android之路】【番外篇】关于==和equals

    [重走Android之路][番外篇]关于==和equals   在实际的编程当中,经常会使用==和equals来判断变量是否相同.但是这两种比较方式也常常让人搞得云里雾里摸不着头脑.下面是我个人做的总 ...

  8. 【重走Android之路】【番外篇】有关于null的一些知识点

    [重走Android之路][番外篇]有关于null的一些知识点   1.首先,到底什么是null? null是Java中的一个关键字,用于表示一个空对象引用,但其本身并不是任何类型也不是属于任何对象. ...

  9. 番外篇 之 C#委托

    对于上一节 番外篇之C#多线程的反思 反思一:   Thread th = new Thread(参数); ////参数的总结 ////首先,第一情况,对于 Thread th = new Threa ...

随机推荐

  1. NOIP 模拟 $18\; \rm 导弹袭击$

    题解 \(by\;zj\varphi\) 一道凸包题 对于每个导弹,它的飞行时间就是 \(tim=\frac{A}{a_i}+\frac{B}{b_i}\) 我们设 \(x=\frac{1}{a_i} ...

  2. 真.OI宝典

    记得取模%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ...

  3. spring security 入门级全篇代码

    CustomAccessDecisionManager 类 ---------------------------------------------------------------------- ...

  4. 单例模式-案例Runtime

    package d.create_type_single; import java.io.IOException; /** * Runtime类就是使用的单例:并且是饿汉式 * (原因考虑是因为:多线 ...

  5. Ubuntu下 QT中配置ROS-Kinetic

    打开qtcreater自动加载ros环境,通过修改*.desktop文件 gedit ~/.local/share/applications/qtcreator.desktop 将其中Exec=XXX ...

  6. MySQL高可用主从复制部署

    原文转自:https://www.cnblogs.com/itzgr/p/10233932.html作者:木二 目录 一 基础环境 二 实际部署 2.1 安装MySQL 2.2 初始化MySQL 2. ...

  7. Panel添加边框颜色和边框粗细调整

    Panel控件添加边框颜色 C# WinForm窗体控件Panel修改边框颜色以及边框宽度方法 - JiYF - 博客园 (cnblogs.com) 1.新建一个用户控件的项目,如下: 2.添加一个P ...

  8. UOS LoongArch 上成功安装.NET Core 3.1

    龙芯.NET团队正式发布了.NET Core 3.1 For LoongArch, 具体参见龙芯开源网站 http://www.loongnix.cn/index.php/Dotnet . 进入安装包 ...

  9. 性能测试工具JMeter 基础(五)—— 测试元件: 测试计划

    测试计划的定义: 测试计划是测试脚本的容器,定义了要执行什么.怎么执行对测试做总体的设置,且都是从线程组开始执行 在测试计划中可自定义用户变量(User Defined Variables),可通过A ...

  10. elementUI+nodeJS环境搭建

    一. ElementUI简介 我们学习VUE,知道它的核心思想式组件和数据驱动,但是每一个组件都需要自己编写模板,样式,添加事件,数据等是非常麻烦的, 所以饿了吗推出了基于VUE2.0的组件库,它的名 ...