利用EF6简单实现多租户的应用

来源:脚本之家  责任编辑:小易  

谢谢邀请!性格本身没有好坏,如果你认为自己有调整的必要,那我可以提三点建议:一、自己最了解自己,告诉自己你是什么样子的人,你是否和谁都很闷,没有任何事情让你变的活跃健谈?如果这个问题的答案是肯定的,你就是一个内向的人,你再问问自己对自己的这种状态感觉不适吗?舒服吗?如果你觉得这样很自然也很舒服,不要逼迫自己去改变,变成一个表面健谈活跃的人,不会是你的初衷,你也不会感到开心,做自己最好。二、想想什么事情让你最开心,你的爱好是什么?在做喜欢做的事情时候你是否变的主动健谈?如果是,那就在相亲的时候把话题引导到自己感兴趣的领域,让对方看到你的另一面,这样的你说不定也是她喜欢的。三、不管一个人性格开朗与

什么是多租户

真的,今天极挑全员抵达哈尔滨机场!也有昨天到的!机场都有路透!男人帮们还是很聪明的穿的很厚,哈尔滨很冷哦!别以为才八月就不冷,我们这边温度已经快到个位数了!极挑录制流程!对比极挑,女神们就没那么好运,前一秒宋茜还在机场凹造型,后一秒冻成表情包!真不懂这个节目的意义是?凹造型?看着都冷!节目也很无聊,在哈尔滨火车站有什么好凹的?等节目出来看看吧!旁边都穿着羽绒服呢!心疼女神!

网上有好多解释,有些上升到了架构设计,让你觉得似乎非常高深莫测,特别是目前流行的ABP架构中就有提到多租户(IMustHaveTenant),其实说的简单一点就是再每一张数据库的表中添加一个TenantId的字段,用于区分属于不同的租户(或是说不同的用户组)的数据。关键是现实的方式必须对开发人员来说是透明的,不需要关注这个字段的信息,由后台或是封装在基类中实现数据的筛选和更新。

有句港话确实是这么说,如果一个人七八年都不换车,只有一个原因——穷。人们不会从车况的角度和人的生活态度考虑,而是会觉得这个人可能“混得不好”,没钱换车。然而,真是这样吗?我看是不一定吧,一千个人当中是有一千种生活方式的。确实有这样一些人,他们对车有很大的喜好和较高的追求,所以在赚了钱之后迫不及待地换车,这样做也无非是为了满足自己的虚荣心,以此当作一种社交的符号。当我们见惯了此种选择,难免会让我们产生一些想法:那些7年都不换车的人是因为穷,没钱再购车。从这个问题的详细描述中可以看的出来,你的这个朋友买东西是有计划的,买东西打折对比,一个是打折大家都知道是便宜,二是对比才知道好坏。对于他而言,可能

基本原理

从新用户注册时就必须指定用户的TenantId,我的例子是用CompanyId,公司信息做为TenantId,哪些用户属于不同的公司,每个用户将来只能修改和查询属于本公司的数据。

看到一组记录农村歌舞团的图片,其中有一张引起了我的注意。歌舞团的一位女演员在腰间系了一根红绳子。这不是我第一次看到有女孩子在腰间扎红绳了,她们这样做是因为什么呢?好看嘛?没觉得啊。风俗?我们汉人的传统哪有女人随便露肚皮的?或者,一种迷信?我一直很好奇。刚好在MSN遇到一位老法师,这人见多识广,对歪门邪道一向很精通,便问他了:问:给你看一张图片,为什么这些女人的腰间要系一根红绳子?答:哦,这个啊。里面的学问很大啊。各地各风俗,各人各说法。问:具体说说。答:有说和本命年有关,有说为了怀孕,也有说为了防止肚子疼,或者妇科病什么的。但是……问:但是什么?答:也有一种,在桑拿浴室经常遇到的那些失足妇女,

接下来就是用户登录的时候获取用户信息的时候把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,这个是免费开源的一个类库,功能强大

public StoreContext() : base("Name=DefaultConnection") { //获取登录用户信息,tenantid var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity; var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); var tenantid = Convert.ToInt32(tenantclaim?.Value); //设置当对Work对象进行查询时默认添加过滤条件 QueryFilterManager.Filter<Work>(q => q.Where(x => x.TenantId == tenantid)); //设置当对Order对象进行查询时默认添加过滤条件 QueryFilterManager.Filter<Order>(q => q.Where(x => x.TenantId == tenantid)); } public override Task<int> SaveChangesAsync(CancellationToken cancellationToken) { var currentDateTime = DateTime.Now; var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity; var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); var tenantid = Convert.ToInt32(tenantclaim?.Value); foreach (var auditableEntity in this.ChangeTracker.Entries<Entity>()) { if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified) { //auditableEntity.Entity.LastModifiedDate = currentDateTime; switch (auditableEntity.State) { case EntityState.Added: auditableEntity.Property("LastModifiedDate").IsModified = false; auditableEntity.Property("LastModifiedBy").IsModified = false; auditableEntity.Entity.CreatedDate = currentDateTime; auditableEntity.Entity.CreatedBy = claimsidentity.Name; auditableEntity.Entity.TenantId = tenantid; break; case EntityState.Modified: auditableEntity.Property("CreatedDate").IsModified = false; auditableEntity.Property("CreatedBy").IsModified = false; auditableEntity.Entity.LastModifiedDate = currentDateTime; auditableEntity.Entity.LastModifiedBy = claimsidentity.Name; auditableEntity.Entity.TenantId = tenantid; //if (auditableEntity.Property(p => p.Created).IsModified || auditableEntity.Property(p => p.CreatedBy).IsModified) //{ // throw new DbEntityValidationException(string.Format("Attempt to change created audit trails on a modified {0}", auditableEntity.Entity.GetType().FullName)); //} break; } } } return base.SaveChangesAsync(cancellationToken); } public override int SaveChanges() { var currentDateTime = DateTime.Now; var claimsidentity =(ClaimsIdentity)HttpContext.Current.User.Identity; var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); var tenantid = Convert.ToInt32(tenantclaim?.Value); foreach (var auditableEntity in this.ChangeTracker.Entries<Entity>()) { if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified) { auditableEntity.Entity.LastModifiedDate = currentDateTime; switch (auditableEntity.State) { case EntityState.Added: auditableEntity.Property("LastModifiedDate").IsModified = false; auditableEntity.Property("LastModifiedBy").IsModified = false; auditableEntity.Entity.CreatedDate = currentDateTime; auditableEntity.Entity.CreatedBy = claimsidentity.Name; auditableEntity.Entity.TenantId = tenantid; break; case EntityState.Modified: auditableEntity.Property("CreatedDate").IsModified = false; auditableEntity.Property("CreatedBy").IsModified = false; auditableEntity.Entity.LastModifiedDate = currentDateTime; auditableEntity.Entity.LastModifiedBy = claimsidentity.Name; auditableEntity.Entity.TenantId = tenantid; break; } } } return base.SaveChanges(); } DbContext.cs

经过以上3步就实现一个简单的多租户查询数据的功能。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

扩展阅读,根据您访问的内容系统为您准备了以下内容,希望对您有帮助。

多租户技术的实务应用

多租户技术在实务上运用的成功且广为人知的案例之一,是由Salesforce所建置的CRM应用系统,该公司除了Salesforce的CRM软件以外,它还建置了Force平台即服务(PaaS)架构,以支持开发人员发展基于Force平台上的应用程序。

在云计算的加持之下,多租户技术被广为运用于开发云各式服务,不论是IaaS,PaaS还是SaaS,都可以看到多租户技术的影子。

多租户架构下的客户化配置是怎样实现的

您好,

1、对于小型的新兴公司或单个开发人员来说,如果想以负担得起的成本价格快速开发应用程序,并创建概念验证以及Web应用程序的小部件,那么云计算多租户非常合适,随着时代的发展,这已成为一种不可避免的趋势。

2、对小部件和Web应用程序用户来说,可伸缩性和上市时间有很高的优先级。例如,用于移动设备或个人用途的应用程序,甚至是安全要求较低的小企业应用程序,它们的使用都是基于创新、响应时间和易用性,而不是安全性和高可用性。

3、如果云多租户应用程序更加安全,并能改善响应时间,那么这些组织就能交付大型企业解决方案,并满足这些组织的需求。例如,如果使用本文描述的步骤来创建多租户并将它集成到企业组织中,则需要使用隔离来解决每种类型服务可用性的安全问题,以防止拒绝服务问题和特定的服务需求。多租户可防止服务在受到端对端事务攻击时失效,这在以后肯能会用到,但我们在本文中不打算介绍这方面的内容。

thinkphp 支持多租户吗

不支持

在云领域我们常常会听到一个词:多租户。这个词在不同的语境中有着不同的含义。本文将介绍云平台中的多租户的概念以及实现多租户支持的思路。

什么是租户

刚开始接触这个概念时,你肯定感觉“租户”这个词怪怪的。但假设我们换个词,我相信你立即就有感觉了。这个词就是“客户”(这里的客户指的就是商业上面的客户)。

一个租户就是一个客户,比方我们开发的服务是给 XXX 企业使用的,那该企业就是我们的一个客户/租户;假设这个服务是面向互联网的,那么使用该服务的每一个互联网用户都是一个客户/租户。

为什么须要多租户支持

开发人员辛辛苦苦开发出一个服务。提供给了个人/企业使用,这样就完事了么?当然不应该仅仅是这样。我们开发出一个服务。最好是可以同一时候提供给多个个人/企业使用。并且这些客户最好是共享同一套服务执行时(Runtime),这样可以大大减少服务的运维成本:

  • 服务执行时假设分开,则运维的成本与客户数成正比(比方更新部署大量客户的场景)

  • 节省资源(将服务所需资源利用最大化:运维团队统一、硬件使用)

  • 另外,这样也能够减少服务的开发成本:

  • 我们仅仅须要考虑怎样实现单用户的服务逻辑:业务逻辑相应其全部客户都是同样的,不管什么客户来使用,程序提供的服务都是一样的。进一步说,在业务层面我们开发这个服务时理论上不须要考虑多客户支持,我们仅仅用关注该服务的业务逻辑怎样实现

  • 多客户的管理功能能够进行统一:开发人员应该不用考虑客户管理功能,这部分应该是由云平台统一提供的

  • 多租户场景举例

    如果我们要开发的服务是一个博客平台,这个服务是面向互联网用户的,每一个互联网用户都是我们的客户(一个用户就是一个租户)。

    在不支持多租户的环境中,为了隔离每一个用户的数据,至少我们在设计数据库表时会考虑大多数表都存在一个 user_id 字段。用于 CRUD 数据时使用该字段进行用户隔离。

    比方如今的业务是“公布文章”。须要将文章数据保存在 article 表中,在实现时实际上我们关注了两件事情:

  • CRUD:这是业务逻辑实现的一部分

  • 用户隔离:须要增加 user_id。做业务关联

  • 1 是“纯”业务逻辑部分的实现。这是必须实现的;2 则是为了多用户博客平台而须要考虑的,这并非博客平台本身的业务逻辑。这里假设能得到平台的多租户支持,就不用考虑第 2 点了。这样能够将注意力集中于第 1 点业务逻辑实现上,这是很典型的一个多租户场景。

    多租户支持

    我们能够这样理解多租户支持:

  • 从服务提供的角度看。我们开发的一个服务执行时能够同一时候提供给多个客户使用。而且客户之间的数据/状态是保持隔离的

  • 从服务使用的角度看,我和你能够作为不同的客户同一时候使用同一个执行的服务,此时我们使用该服务完毕的业务是相互不影响的,就好像我们在使用自己独享的服务一样

  • 那么这个服务就是支持多“客户”的,即该服务支持多租户。这里的“服务”能够是应用,能够是 SaaS 平台,也能够是 PaaS 平台。只是按眼下我们熟悉的云平台看,应用的多租户支持应该是最常规的。这是由于应用面向的是用户,这个群体是非常庞大的。

    多租户支持从实现的角度看。“是一种软件架构技术”,之所以强调它是属于架构层面是由于要实现它必须在做技术架构时就要将其考虑在内。

    一种租户模型

    本文一开始我们提到使用“客户”来置换“租户”来理解租户的含义。再从“商业”这个方面来看的话,我们不难发现租户事实上就是其云环境中的商业模式实现的一部分。商业模式是多样的。这意味着租户的划分也是多样的。这里我们描写叙述当中一种可能的租户栈:

  • 应用程序是提供给用户使用的,对于应用来说,用户就是它的租户(这一点业界比较统一)

  • SaaS 提供的服务是给应用开发商使用的,对于 SaaS 来说,应用开发商就是它的租户

  • PaaS 提供的服务是给应用系统使用的,对于 PaaS 来说。相关应用的组合就是它的租户

  • SaaS 和 PaaS 面向的是开发商、系统等非端用户角色。这一部分通常是由云平台开发人员决定的(*商业模式)。特别是私有/企业云平台一般不会考虑形如“在 PaaS 平台上支持执行多个 SaaS 平台”这种场景。所以以下我们很多其它的是环绕“应用对多租户支持”进行讨论。

    应用多租户

    应用多租户的使用场景前面已经介绍过了。如今如果我们是一个云平台开发人员,为了满足支持应用支持多租户的需求,在云平台中我们须要提供以下几个支持:

  • 租户管理:CRUD,统计

  • 租户隔离/共享的服务:队列、缓存、数据库等

  • 租户隔离的统计:日志、配额

  • 这些支持能够分为两类:

  • 租户的管理:不会直接面向应用的端用户。面向的是应用的运维。平台应该提供详细实现

  • 租户数据/状态的隔离:从请求开始就应该能够区分这个请求是来自于哪个租户,请求处理时在调用链路上也须要带上租户上下文。数据的存取是依照租户隔离的。调用平台提供的服务时也是租户隔离的

  • 第 1 点比较easy实现。这是一个业务模型方面的问题,能够依据业务域来抽象租户模型,比方企业应用通常是依照“组织机构”来区分租户的;

    第 2 点是一个纯技术的需求。须要在平台技术实现上支持按“租户”的执行时隔离,我们强调的是隔离,由于在实现时我们要达到的目标就是隔离,仅仅只是这里是按租户(租户仅仅是一个商业概念,技术层面我们最好能够将其进行抽象。尽量减小商业模式多样化对技术架构的冲击)。我们能够将租户映射到一个抽象概念上,这个抽象概念能够实现我们的隔离需求。

什么是SaaS应用OFBIZ多租户模式

  SaaS现在已成为一股潮流,它将颠覆传统的软件交付方式

  其实从架构层面来分析,SaaS区别于传统技术的重要差别就是Multi-Tenant模式。多租户就是说多个租户共用一个实例,租户的数据既有隔离又有共享,说到底就是如何解决数据存储的问题。

  现在SaaS Multi-Tenant在数据存储上存在三种主要的方案,分别是—

  方案一:独立数据库

  这是第一种方案,即一个Tenant一个Database(见图3-14),这种方案的用户数据隔离级别最高,安全性最好,但成本也高。

  优点:

  为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;如果出现故障,恢复数据比较简单。

  缺点:

  增大了数据库的安装数量,随之带来维护成本和购置成本的增加。

  这种方案与传统的一个客户、一套数据、一套部署类似,差别只在于软件统一部署在运营商那里。如果面对的是银行、医院等需要非常高数据隔离级别的租户,可以选择这种模式,提高租用的定价。如果定价较低,产品走低价路线,这种方案一般对运营商来说是无法承受的。

  方案二:共享数据库,隔离数据架构.即多个或所有租户共享Database,但一个Tenant一个Schema。

  优点:

  为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可以支持更多的租户数量。

  缺点:

  如果出现故障,数据恢复比较困难,因为恢复数据库将牵扯到其他租户的数据;如果需要跨租户统计数据,存在一定困难。

  方案三:共享数据库,共享数据架构.即租户共享同一个Database、同一个Schema,但在表中通过TenantID区分租户的数据。这是共享程度最高、隔离级别最低的模式。

  优点:

  三种方案比较,第三种方案的维护和购置成本最低,允许每个数据库支持的租户数量最多。

  缺点:

  隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量;数据备份和恢复最困难,需要逐表逐条备份和还原。如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本,这种方案最适合。

  CRM系统未来将以中低端市场为主,所以采用第三种方案,只要做好数据隔离比较好了。千万不可掉以轻心,SaaS下的安全性设计很重要。一般常见的安全性设计分为两类:系统级和程序级。

  系统级:

  使用HTTPS协议以SSL(Security Socket Layer)交换数据,增强通信安全;通过数字签名防止传输过程篡改;对用户身份识别的UserToken使用DES算法数据加密;业务数据定时自动备份。

  程序级:

  完整的权限配置,包括功能权限和数据权限;客户端输入校验,防止JS攻击、XSS攻击、SQL注入等;辅助安全设计,比如密码控件、图片验证码、手机确认码等。

  • 本文相关:
  • 一个简单mvc5 + ef6示例分享
  • asp.net 动态生成rdlc报表(原创)
  • asp.net中viewstate的用法详解
  • silverlightbutton图片切换样式实例代码
  • 未能加载文件或程序集“xxx”或它的某一个依赖项。试图加载格式不
  • asp.net 程序性能优化的七个方面 (c#(或vb.net)程序改进)
  • asp.net mvc后台参数验证的几种方式
  • asp.net中获取datatable选择第一行某一列值
  • 三层+存储过程实现分页示例代码
  • asp.net中gridview编辑,更新,合计用法示例
  • gridview调整单元格宽度的方法
  • 多租户技术的实务应用
  • 多租户架构下的客户化配置是怎样实现的
  • thinkphp 支持多租户吗
  • 什么是SaaS应用OFBIZ多租户模式
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved