利用EF6簡單實現(xiàn)多租戶的應(yīng)用
什么是多租戶
網(wǎng)上有好多解釋,有些上升到了架構(gòu)設(shè)計,讓你覺得似乎非常高深莫測,特別是目前流行的ABP架構(gòu)中就有提到多租戶(IMustHaveTenant),其實說的簡單一點就是再每一張數(shù)據(jù)庫的表中添加一個TenantId的字段,用于區(qū)分屬于不同的租戶(或是說不同的用戶組)的數(shù)據(jù)。關(guān)鍵是現(xiàn)實的方式必須對開發(fā)人員來說是透明的,不需要關(guān)注這個字段的信息,由后臺或是封裝在基類中實現(xiàn)數(shù)據(jù)的篩選和更新。
基本原理
從新用戶注冊時就必須指定用戶的TenantId,我的例子是用CompanyId,公司信息做為TenantId,哪些用戶屬于不同的公司,每個用戶將來只能修改和查詢屬于本公司的數(shù)據(jù)。
接下來就是用戶登錄的時候獲取用戶信息的時候把TenantId保存起來,asp.net mvc(不是 core) 是通過 Identity 2.0實現(xiàn)的認證和授權(quán),這里需要重寫部分代碼來實現(xiàn)。
最后用戶對數(shù)據(jù)查詢/修改/新增時把用戶信息中TenantId,這里就需要設(shè)定一個Filter(過濾器)和每次SaveChange的插入TenantId
如何實現(xiàn)
第一步,擴展 Asp.net Identity user 屬性,必須新增一個TenantId字段,根據(jù)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);
//設(shè)置當(dāng)對Work對象進行查詢時默認添加過濾條件
QueryFilterManager.Filter<Work>(q => q.Where(x => x.TenantId == tenantid));
//設(shè)置當(dāng)對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
經(jīng)過以上3步就實現(xiàn)一個簡單的多租戶查詢數(shù)據(jù)的功能。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
DataTable類Clone方法與Copy方法的區(qū)別分析
初學(xué)者可能不清楚DataTable類的Clone及Copy方法的區(qū)別,查msdn,可得到如下結(jié)論2013-03-03
IP地址與整數(shù)之間的轉(zhuǎn)換實現(xiàn)代碼(asp.net)
把這個整數(shù)轉(zhuǎn)換成一個32位二進制數(shù)。從左到右,每8位進行一下分割,得到4段8位的二進制數(shù),把這些二進制數(shù)轉(zhuǎn)換成整數(shù)然后加上”。”就是這個ip地址了2012-09-09
asp.net 安全的截取指定長度的html或者ubb字符串
在將html代碼輸出到頁面時,有時候會需要截斷字符串保留指定長度的字符串,由于html中有些標(biāo)簽必須成對出現(xiàn),所以在截取html時需要特別注意,不能因為截斷問題把頁面搞亂掉。2010-01-01
silverlight用webclient大文件上傳的實例代碼
這篇文章介紹了silverlight用webclient大文件上傳的實例代碼,有需要的朋友可以參考一下2013-10-10

