时间:2021-05-25
什么是多租户
网上有好多解释,有些上升到了架构设计,让你觉得似乎非常高深莫测,特别是目前流行的ABP架构中就有提到多租户(IMustHaveTenant),其实说的简单一点就是再每一张数据库的表中添加一个TenantId的字段,用于区分属于不同的租户(或是说不同的用户组)的数据。关键是现实的方式必须对开发人员来说是透明的,不需要关注这个字段的信息,由后台或是封装在基类中实现数据的筛选和更新。
基本原理
从新用户注册时就必须指定用户的TenantId,我的例子是用CompanyId,公司信息做为TenantId,哪些用户属于不同的公司,每个用户将来只能修改和查询属于本公司的数据。
接下来就是用户登录的时候获取用户信息的时候把TenantId保存起来,asp.net mvc(不是 core) 是通过 Identity 2.0实现的认证和授权,这里需要重写部分代码来实现。
最后用户对数据查询/修改/新增时把用户信息中TenantId,这里就需要设定一个Filter(过滤器)和每次SaveChange的插入TenantId
如何实现
第一步,扩展 Asp.net Identity user 属性,必须新增一个TenantId字段,根据Asp.net Mvc 自带的项目模板修改IdentityModels.cs 这个文件
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. public class ApplicationUser : IdentityUser { public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType) { // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType var userIdentity = await manager.CreateIdentityAsync(this, authenticationType); // Add custom user claims here userIdentity.AddClaim(new Claim("http://schemas.microsoft.com/identity/claims/tenantid", this.TenantId.ToString())); userIdentity.AddClaim(new Claim("CompanyName", this.CompanyName)); userIdentity.AddClaim(new Claim("EnabledChat", this.EnabledChat.ToString())); userIdentity.AddClaim(new Claim("FullName", this.FullName)); userIdentity.AddClaim(new Claim("AvatarsX50", this.AvatarsX50)); userIdentity.AddClaim(new Claim("AvatarsX120", this.AvatarsX120)); return userIdentity; } public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) { // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here return userIdentity; } [Display(Name = "全名")] public string FullName { get; set; } [Display(Name = "性别")] public int Gender { get; set; } public int AccountType { get; set; } [Display(Name = "所属公司")] public string CompanyCode { get; set; } [Display(Name = "公司名称")] public string CompanyName { get; set; } [Display(Name = "是否在线")] public bool IsOnline { get; set; } [Display(Name = "是否开启聊天功能")] public bool EnabledChat { get; set; } [Display(Name = "小头像")] public string AvatarsX50 { get; set; } [Display(Name = "大头像")] public string AvatarsX120 { get; set; } [Display(Name = "租户ID")] public int TenantId { get; set; } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) => Database.SetInitializer<ApplicationDbContext>(null); public static ApplicationDbContext Create() => new ApplicationDbContext(); }第二步 修改注册用户的代码,注册新用户的时候需要选择所属的公司信息
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(AccountRegistrationModel viewModel) { var data = this._companyService.Queryable().Select(x => new ListItem() { Value = x.Id.ToString(), Text = x.Name }); this.ViewBag.companylist = data; // Ensure we have a valid viewModel to work with if (!this.ModelState.IsValid) { return this.View(viewModel); } // Try to create a user with the given identity try { // Prepare the identity with the provided information var user = new ApplicationUser { UserName = viewModel.Username, FullName = viewModel.Lastname + "." + viewModel.Firstname, CompanyCode = viewModel.CompanyCode, CompanyName = viewModel.CompanyName, TenantId=viewModel.TenantId, Email = viewModel.Email, AccountType = 0 }; var result = await this.UserManager.CreateAsync(user, viewModel.Password); // If the user could not be created if (!result.Succeeded) { // Add all errors to the page so they can be used to display what went wrong this.AddErrors(result); return this.View(viewModel); } // If the user was able to be created we can sign it in immediately // Note: Consider using the email verification proces await this.SignInAsync(user, true); return this.RedirectToLocal(); } catch (DbEntityValidationException ex) { // Add all errors to the page so they can be used to display what went wrong this.AddErrors(ex); return this.View(viewModel); } }AccountController.cs第三步 读取登录用户的TenantId 在用户查询和新增修改时把TenantId插入到表中,这里需要引用
Z.EntityFramework.Plus,这个是免费开源的一个类库,功能强大
经过以上3步就实现一个简单的多租户查询数据的功能。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
解决方案中的数据层项目最初使用的是oracle11g+ef5创建的实体模型,在分页时遇到了skip参数为0报错的问题,没有找到相关资料。于是决定升级到ef6,在
最近在做SaaS应用,数据库采用了单实例多schema的架构(详见参考资料1),每个租户有一个独立的schema,同时整个数据源有一个共享的schema,因此需
之前那篇文章介绍了ASP.NETMVC使用EF来查询数据和EF中DbQuery泛型对象对数据的延迟加载。今天我们就来看看我们怎么使用EF来删除数据。其实现在的W
此前,我们花费了两年的时间研发了一套教学系统,考虑到用户的数量与营运成本,后期决定将这套单体的应用程序改造为基于saas架构的多租户应用程序。经过短暂的需求分析
动态数据源在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库。又比如业务A要访问A数据库