利用EF6簡(jiǎn)單實(shí)現(xiàn)多租戶(hù)的應(yīng)用
什么是多租戶(hù)
網(wǎng)上有好多解釋?zhuān)行┥仙搅思軜?gòu)設(shè)計(jì),讓你覺(jué)得似乎非常高深莫測(cè),特別是目前流行的ABP架構(gòu)中就有提到多租戶(hù)(IMustHaveTenant),其實(shí)說(shuō)的簡(jiǎn)單一點(diǎn)就是再每一張數(shù)據(jù)庫(kù)的表中添加一個(gè)TenantId的字段,用于區(qū)分屬于不同的租戶(hù)(或是說(shuō)不同的用戶(hù)組)的數(shù)據(jù)。關(guān)鍵是現(xiàn)實(shí)的方式必須對(duì)開(kāi)發(fā)人員來(lái)說(shuō)是透明的,不需要關(guān)注這個(gè)字段的信息,由后臺(tái)或是封裝在基類(lèi)中實(shí)現(xiàn)數(shù)據(jù)的篩選和更新。
基本原理
從新用戶(hù)注冊(cè)時(shí)就必須指定用戶(hù)的TenantId,我的例子是用CompanyId,公司信息做為T(mén)enantId,哪些用戶(hù)屬于不同的公司,每個(gè)用戶(hù)將來(lái)只能修改和查詢(xún)屬于本公司的數(shù)據(jù)。
接下來(lái)就是用戶(hù)登錄的時(shí)候獲取用戶(hù)信息的時(shí)候把TenantId保存起來(lái),asp.net mvc(不是 core) 是通過(guò) Identity 2.0實(shí)現(xiàn)的認(rèn)證和授權(quán),這里需要重寫(xiě)部分代碼來(lái)實(shí)現(xiàn)。
最后用戶(hù)對(duì)數(shù)據(jù)查詢(xún)/修改/新增時(shí)把用戶(hù)信息中TenantId,這里就需要設(shè)定一個(gè)Filter(過(guò)濾器)和每次SaveChange的插入TenantId
如何實(shí)現(xiàn)
第一步,擴(kuò)展 Asp.net Identity user 屬性,必須新增一個(gè)TenantId字段,根據(jù)Asp.net Mvc 自帶的項(xiàng)目模板修改IdentityModels.cs 這個(gè)文件
// 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 = "公司名稱(chēng)")] public string CompanyName { get; set; } [Display(Name = "是否在線")] public bool IsOnline { get; set; } [Display(Name = "是否開(kāi)啟聊天功能")] public bool EnabledChat { get; set; } [Display(Name = "小頭像")] public string AvatarsX50 { get; set; } [Display(Name = "大頭像")] public string AvatarsX120 { get; set; } [Display(Name = "租戶(hù)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(); }
第二步 修改注冊(cè)用戶(hù)的代碼,注冊(cè)新用戶(hù)的時(shí)候需要選擇所屬的公司信息
[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
第三步 讀取登錄用戶(hù)的TenantId 在用戶(hù)查詢(xún)和新增修改時(shí)把TenantId插入到表中,這里需要引用
Z.EntityFramework.Plus,這個(gè)是免費(fèi)開(kāi)源的一個(gè)類(lèi)庫(kù),功能強(qiáng)大
public StoreContext() : base("Name=DefaultConnection") { //獲取登錄用戶(hù)信息,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); //設(shè)置當(dāng)對(duì)Work對(duì)象進(jìn)行查詢(xún)時(shí)默認(rèn)添加過(guò)濾條件 QueryFilterManager.Filter<Work>(q => q.Where(x => x.TenantId == tenantid)); //設(shè)置當(dāng)對(duì)Order對(duì)象進(jìn)行查詢(xún)時(shí)默認(rèn)添加過(guò)濾條件 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
經(jīng)過(guò)以上3步就實(shí)現(xiàn)一個(gè)簡(jiǎn)單的多租戶(hù)查詢(xún)數(shù)據(jù)的功能。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
asp.net 文章分頁(yè)顯示實(shí)現(xiàn)代碼
asp.net 文章分頁(yè)顯示實(shí)現(xiàn)代碼,不多說(shuō)看代碼,簡(jiǎn)單,自己請(qǐng)適當(dāng)修改2012-06-06DataTable類(lèi)Clone方法與Copy方法的區(qū)別分析
初學(xué)者可能不清楚DataTable類(lèi)的Clone及Copy方法的區(qū)別,查msdn,可得到如下結(jié)論2013-03-03IP地址與整數(shù)之間的轉(zhuǎn)換實(shí)現(xiàn)代碼(asp.net)
把這個(gè)整數(shù)轉(zhuǎn)換成一個(gè)32位二進(jìn)制數(shù)。從左到右,每8位進(jìn)行一下分割,得到4段8位的二進(jìn)制數(shù),把這些二進(jìn)制數(shù)轉(zhuǎn)換成整數(shù)然后加上”?!本褪沁@個(gè)ip地址了2012-09-09asp.net 安全的截取指定長(zhǎng)度的html或者ubb字符串
在將html代碼輸出到頁(yè)面時(shí),有時(shí)候會(huì)需要截?cái)嘧址A糁付ㄩL(zhǎng)度的字符串,由于html中有些標(biāo)簽必須成對(duì)出現(xiàn),所以在截取html時(shí)需要特別注意,不能因?yàn)榻財(cái)鄦?wèn)題把頁(yè)面搞亂掉。2010-01-01silverlight用webclient大文件上傳的實(shí)例代碼
這篇文章介紹了silverlight用webclient大文件上傳的實(shí)例代碼,有需要的朋友可以參考一下2013-10-10