利用EF6简单实现多租户的应用
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
什么是多租户
网上有好多解释,有些上升到了架构设计,让你觉得似乎非常高深莫测,特别是目前流行的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 这个文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | // 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(); } |
第二步 修改注册用户的代码,注册新用户的时候需要选择所属的公司信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | [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,这个是免费开源的一个类库,功能强大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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步就实现一个简单的多租户查询数据的功能。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
asp.net 安全的截取指定长度的html或者ubb字符串
在将html代码输出到页面时,有时候会需要截断字符串保留指定长度的字符串,由于html中有些标签必须成对出现,所以在截取html时需要特别注意,不能因为截断问题把页面搞乱掉。2010-01-01silverlight用webclient大文件上传的实例代码
这篇文章介绍了silverlight用webclient大文件上传的实例代码,有需要的朋友可以参考一下2013-10-10
最新评论