说明

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. 盘点 HashMap 的实现原理及面试题

    1.请你谈谈 HashMap 的工作原理如果被问到 HashMap 相关的问题,它的工作原理都会被作为面试的开场白,这个时候先装作若有所思的样子冷静一下.首先 HashMap 是基于 hashing ...

  2. bootstrap 冻结表格,冻结表头

    需要的文件下载: bootstrap-table:https://github.com/wenzhixin/bootstrap-table bootstrap-table-fiex-column:ht ...

  3. HDFS简介及基本概念

    (一)HDFS简介及其基本概念   HDFS(Hadoop Distributed File System)是hadoop生态系统的一个重要组成部分,是hadoop中的的存储组件,在整个Hadoop中 ...

  4. Map 综述(二):彻头彻尾理解 LinkedHashMap

    摘要: HashMap和双向链表合二为一即是LinkedHashMap.所谓LinkedHashMap,其落脚点在HashMap,因此更准确地说,它是一个将所有Entry节点链入一个双向链表的Hash ...

  5. The First Week luckyzpp

    一 ,LINUX系列有很多版本,只是我们很少去了解到更多,我们熟知红帽CentOS,Ubuntu,Debian,   Kali,  Rocky各种版本系列. 二 目前Linux 生产主流版本如下: C ...

  6. 新东方APP技术架构演进, 分布式系统架构经验分享

    今天的演讲题目是"新东方APP技术架构演进, C端技术经验分享" 作者:张建鑫, 曾任IBM高级软件架构师, 滴滴高级技术专家, 现任新东方集团高级技术总监 古代东西方的思想家都产 ...

  7. Spring Boot +Vue 项目实战笔记(一):使用 CLI 搭建 Vue.js 项目

    前言 从这篇文章开始,就进入真正的实践了. 在前端项目开发中,我们可以根据实际情况不同程度地使用 Vue.利用 Vue CLI(或写成 vue-cli,即 Vue 脚手架)搭建出来的项目,是最能体现 ...

  8. mysql switch语句

    SELECT CASE the_order_status WHEN 4 THEN '待收货' WHEN 5 THEN '已收货' ELSE '其他' END AS statuss ,order_id ...

  9. php检测数组长度的函数sizeof count

    php教程检测数组长度的函数sizeof count在php检测数组长度的函数有sizeof  count 下面看个简单实例*/$colorlist = array("apple" ...

  10. 4.React生命周期

    4.React生命周期 4.1引出生命周期 class Life extends React.Component { state = { opacity:0.5 } death = () => ...