使用EF6簡實現多租戶的應用
阿新 • • 發佈:2019-09-09
什麼是多租戶
網上有好多解釋,有些上升到了架構設計,讓你覺得似乎非常高深莫測,特別是目前流行的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 // 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. 2 public class ApplicationUser : IdentityUser 3 { 4 public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType) 5 { 6 // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType 7 var userIdentity = await manager.CreateIdentityAsync(this, authenticationType); 8 // Add custom user claims here 9 userIdentity.AddClaim(new Claim("http://schemas.microsoft.com/identity/claims/tenantid", this.TenantId.ToString())); 10 userIdentity.AddClaim(new Claim("CompanyName", this.CompanyName)); 11 userIdentity.AddClaim(new Claim("EnabledChat", this.EnabledChat.ToString())); 12 userIdentity.AddClaim(new Claim("FullName", this.FullName)); 13 userIdentity.AddClaim(new Claim("AvatarsX50", this.AvatarsX50)); 14 userIdentity.AddClaim(new Claim("AvatarsX120", this.AvatarsX120)); 15 return userIdentity; 16 } 17 public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) 18 { 19 // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType 20 var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); 21 // Add custom user claims here 22 return userIdentity; 23 } 24 25 [Display(Name = "全名")] 26 public string FullName { get; set; } 27 [Display(Name = "性別")] 28 public int Gender { get; set; } 29 public int AccountType { get; set; } 30 [Display(Name = "所屬公司")] 31 public string CompanyCode { get; set; } 32 [Display(Name = "公司名稱")] 33 public string CompanyName { get; set; } 34 [Display(Name = "是否線上")] 35 public bool IsOnline { get; set; } 36 [Display(Name = "是否開啟聊天功能")] 37 public bool EnabledChat { get; set; } 38 [Display(Name = "小頭像")] 39 public string AvatarsX50 { get; set; } 40 [Display(Name = "大頭像")] 41 public string AvatarsX120 { get; set; } 42 [Display(Name = "租戶ID")] 43 public int TenantId { get; set; } 44 } 45 46 47 48 public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 49 { 50 public ApplicationDbContext() 51 : base("DefaultConnection", throwIfV1Schema: false) => Database.SetInitializer<ApplicationDbContext>(null); 52 53 public static ApplicationDbContext Create() => new ApplicationDbContext(); 54 55 56 }
第二步 修改註冊使用者的程式碼,註冊新使用者的時候需要選擇所屬的公司資訊
1 [HttpPost] 2 [AllowAnonymous] 3 [ValidateAntiForgeryToken] 4 public async Task<ActionResult> Register(AccountRegistrationModel viewModel) 5 { 6 var data = this._companyService.Queryable().Select(x => new ListItem() { Value = x.Id.ToString(), Text = x.Name }); 7 this.ViewBag.companylist = data; 8 9 // Ensure we have a valid viewModel to work with 10 if (!this.ModelState.IsValid) 11 { 12 return this.View(viewModel); 13 } 14 15 // Try to create a user with the given identity 16 try 17 { 18 // Prepare the identity with the provided information 19 var user = new ApplicationUser 20 { 21 UserName = viewModel.Username, 22 FullName = viewModel.Lastname + "." + viewModel.Firstname, 23 CompanyCode = viewModel.CompanyCode, 24 CompanyName = viewModel.CompanyName, 25 TenantId=viewModel.TenantId, 26 Email = viewModel.Email, 27 AccountType = 0 28 29 }; 30 var result = await this.UserManager.CreateAsync(user, viewModel.Password); 31 32 // If the user could not be created 33 if (!result.Succeeded) 34 { 35 // Add all errors to the page so they can be used to display what went wrong 36 this.AddErrors(result); 37 38 return this.View(viewModel); 39 } 40 41 // If the user was able to be created we can sign it in immediately 42 // Note: Consider using the email verification proces 43 await this.SignInAsync(user, true); 44 45 return this.RedirectToLocal(); 46 } 47 catch (DbEntityValidationException ex) 48 { 49 // Add all errors to the page so they can be used to display what went wrong 50 this.AddErrors(ex); 51 52 return this.View(viewModel); 53 } 54 }
第三步 讀取登入使用者的TenantId 在使用者查詢和新增修改時把TenantId插入到表中,這裡需要引用
Z.EntityFramework.Plus,這個是免費開源的一個類庫,功能強大
1 public StoreContext() 2 : base("Name=DefaultConnection") { 3 //獲取登入使用者資訊,tenantid 4 var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity; 5 var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); 6 var tenantid = Convert.ToInt32(tenantclaim?.Value); 7 //設定當對Work物件進行查詢時預設新增過濾條件 8 QueryFilterManager.Filter<Work>(q => q.Where(x => x.TenantId == tenantid)); 9 //設定當對Order物件進行查詢時預設新增過濾條件 10 QueryFilterManager.Filter<Order>(q => q.Where(x => x.TenantId == tenantid)); 11 } 12 13 public override Task<int> SaveChangesAsync(CancellationToken cancellationToken) 14 { 15 var currentDateTime = DateTime.Now; 16 var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity; 17 var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); 18 var tenantid = Convert.ToInt32(tenantclaim?.Value); 19 foreach (var auditableEntity in this.ChangeTracker.Entries<Entity>()) 20 { 21 if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified) 22 { 23 //auditableEntity.Entity.LastModifiedDate = currentDateTime; 24 switch (auditableEntity.State) 25 { 26 case EntityState.Added: 27 auditableEntity.Property("LastModifiedDate").IsModified = false; 28 auditableEntity.Property("LastModifiedBy").IsModified = false; 29 auditableEntity.Entity.CreatedDate = currentDateTime; 30 auditableEntity.Entity.CreatedBy = claimsidentity.Name; 31 auditableEntity.Entity.TenantId = tenantid; 32 break; 33 case EntityState.Modified: 34 auditableEntity.Property("CreatedDate").IsModified = false; 35 auditableEntity.Property("CreatedBy").IsModified = false; 36 auditableEntity.Entity.LastModifiedDate = currentDateTime; 37 auditableEntity.Entity.LastModifiedBy = claimsidentity.Name; 38 auditableEntity.Entity.TenantId = tenantid; 39 //if (auditableEntity.Property(p => p.Created).IsModified || auditableEntity.Property(p => p.CreatedBy).IsModified) 40 //{ 41 // throw new DbEntityValidationException(string.Format("Attempt to change created audit trails on a modified {0}", auditableEntity.Entity.GetType().FullName)); 42 //} 43 break; 44 } 45 } 46 } 47 return base.SaveChangesAsync(cancellationToken); 48 } 49 50 public override int SaveChanges() 51 { 52 var currentDateTime = DateTime.Now; 53 var claimsidentity =(ClaimsIdentity)HttpContext.Current.User.Identity; 54 var tenantclaim = claimsidentity?.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid"); 55 var tenantid = Convert.ToInt32(tenantclaim?.Value); 56 foreach (var auditableEntity in this.ChangeTracker.Entries<Entity>()) 57 { 58 if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified) 59 { 60 auditableEntity.Entity.LastModifiedDate = currentDateTime; 61 switch (auditableEntity.State) 62 { 63 case EntityState.Added: 64 auditableEntity.Property("LastModifiedDate").IsModified = false; 65 auditableEntity.Property("LastModifiedBy").IsModified = false; 66 auditableEntity.Entity.CreatedDate = currentDateTime; 67 auditableEntity.Entity.CreatedBy = claimsidentity.Name; 68 auditableEntity.Entity.TenantId = tenantid; 69 break; 70 case EntityState.Modified: 71 auditableEntity.Property("CreatedDate").IsModified = false; 72 auditableEntity.Property("CreatedBy").IsModified = false; 73 auditableEntity.Entity.LastModifiedDate = currentDateTime; 74 auditableEntity.Entity.LastModifiedBy = claimsidentity.Name; 75 auditableEntity.Entity.TenantId = tenantid; 76 break; 77 } 78 } 79 } 80 return base.SaveChanges(); 81 }DbContext.cs
經過以上3步就實現一個簡單的多租戶查詢資料的功能。
希望對大家有