说明

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. [ES6深度解析]12:Classes

    我们将讨论一个老问题:在JavaScript中创建对象的构造函数. 存在的问题 假设我们想要创建最典型的面向对象设计的示例:Circle类.假设我们正在为一个简单的Canvas库编写一个Circle. ...

  2. idea中使用docker插件部署项目

    安装docker 如果你之前安装过 docker,请先删掉 sudo yum remove docker docker-common docker-selinux docker-engine 安装一些 ...

  3. uwp Button的动态效果

    你应该覆盖Button样式 <Page.Resources> <Style TargetType="Button" x:Key="CustomButto ...

  4. COM笔记-CoCreateInstance

    CoCreateInstance 创建组件的最简单的方法是使用CoCreateInstance函数. 在COM库中包含一个用于创建组件的名为CoCreateInstance的函数.此函数需要一个CLS ...

  5. 轻松入门vue系列

    一.vue基础语法 二.vue组件化开发 三.Vue前后端交互 四.vue前端路由 喜欢不要忘了点个赞哟

  6. Linux centos 安装 ftp(Vsftp) 与 设置ftp(Vsftp)

    本文章只是简单搭建,因为公司只须要简单使用,虽然简单但是之前也走了一些弯路,所以决定把过程记录下来. 一.Vsftp安装与卸载 安装:yum install vsftpd 卸载:yum remove ...

  7. 再过五分钟,你就懂 HTTP 2.0 了!

    Hey guys ,各位小伙伴们大家好,这里是程序员 cxuan,欢迎你收看我最新一期的文章. 这篇文章我们来聊一聊 HTTP 2.0,以及 HTTP 2.0 它在 HTTP 1.1 的基础上做了哪些 ...

  8. VS2017 Debug时候出现 Script Error An error has occurred in the script on this page. 解决办法

    解决办法: Menu -> Debug -> Options -> Debugging/General -> 取消最后面的Enable Diagnostic Tools whi ...

  9. Robot framework随机文件

    *** Variables *** @{Example} One Two Three *** Test Cases *** Example ${value}= Evaluate random.choi ...

  10. MySQL——字符串类型——char(n) 和 varchar(n)

    MySQL 的 char(n) 和 varchar(n) 括号中 n 代表字符的个数,而非字节个数,这里说的字符不论文字种类,假设一个字段的数据类型被规定为 char(2),则可以在这个字段上插入 ' ...