利用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 这个文件
// 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); //设置当对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步就实现一个简单的多租户查询数据的功能。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。