使用EF6簡實現多租戶的應用

什麼是多租戶

網上有好多解釋,有些上升到了架構設計,讓你以爲彷佛很是高深莫測,特別是目前流行的ABP架構中就有提到多租戶(IMustHaveTenant),其實說的簡單一點就是再每一張數據庫的表中添加一個TenantId的字段,用於區分屬於不一樣的租戶(或是說不一樣的用戶組)的數據。關鍵是現實的方式必須對開發人員來講是透明的,不須要關注這個字段的信息,由後臺或是封裝在基類中實現數據的篩選和更新。數據庫

基本原理

重新用戶註冊時就必須指定用戶的TenantId,個人例子是用CompanyId,公司信息作爲TenantId,哪些用戶屬於不一樣的公司,每一個用戶未來只能修改和查詢屬於本公司的數據。架構

接下來就是用戶登陸的時候獲取用戶信息的時候把TenantId保存起來,asp.net mvc(不是 core) 是經過 Identity 2.0實現的認證和受權,這裏須要重寫部分代碼來實現。mvc

最後用戶對數據查詢/修改/新增時把用戶信息中TenantId,這裏就須要設定一個Filter(過濾器)和每次SaveChange的插入TenantIdasp.net

如何實現

第一步,擴展 Asp.net Identity user 屬性,必須新增一個TenantId字段,根據Asp.net Mvc 自帶的項目模板修改IdentityModels.cs 這個文件async

 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   }
View Code

 

  第二步 修改註冊用戶的代碼,註冊新用戶的時候須要選擇所屬的公司信息ide

 

 

 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     }
AccountController.cs

第三步 讀取登陸用戶的TenantId 在用戶查詢和新增修改時把TenantId插入到表中,這裏須要引用this

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步就實現一個簡單的多租戶查詢數據的功能。spa

但願對你們有用。.net

相關文章
相關標籤/搜索