使用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 = };
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步就实现一个简单的多租户查询数据的功能。
希望对大家有用。
使用EF6简实现多租户的应用的更多相关文章
- 从头开始一步一步实现EF6+Autofac+MVC5+Bootstarp极简前后台ajax表格展示及分页(二)前端修改、添加表格行点击弹出模态框
在前一篇中,由于不懂jquery,前端做的太差了,今天做稍做修改,增加一个跳转到指定页面功能,表格行点击样式变化.并且在表格中加入bootstarp的按钮组,按钮点击后弹出模态框,须修改common, ...
- 从头开始一步一步实现EF6+Autofac+MVC5+Bootstarp极简的实现前后台ajax表格展示及分页实现
本来是想试着做一个简单OA项目玩玩的,真是不做不知道,一做吓死人,原来以为很简单的事情,但是做起来不是忘这就是忘那的,有的技术还得重新温习.所以还是得记录.免得哪天电脑挂了,就全没有了. 开始是看了园 ...
- EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(一)
前言 本系列源自对EF6 CodeFirst的探索,但后来发现在自己项目中构建的时候遇到了一些问题以及一些解决方法,因此想作为一个系列写下来. 本系列并不是教你怎么做架构设计,但可以参照一下里面的方法 ...
- 如何在多模型的情况下进行EF6的结构迁移
所谓多模型就是在一个数据库中包含两个不同模型,或者换句话说就是两个不同DbContext的数据都放到同一个数据库中.这里的多模型不是指多租户的数据库(有谁知道EF很好处理多租户数据库的方案,可以联系我 ...
- 借助腾讯云的云函数实现一个极简的API网关
借助腾讯云的云函数实现一个极简的API网关 Intro 微信小程序的域名需要备案,但是没有大陆的服务器,而且觉得备案有些繁琐,起初做的小程序都有点想要放弃了,后来了解到腾讯云的云函数,于是利用腾讯云的 ...
- 极简实用的Asp.NetCore模块化框架新增CMS模块
简介 关于这个框架的背景,在前面我已经交代过了.不清楚的可以查看这个链接 极简实用的Asp.NetCore模块化框架决定免费开源了 在最近一段时间内,对这个框架新增了以下功能: 1.新增了CMS模块, ...
- MVC5+EF6+MYSQl,使用codeFirst的数据迁移
之前本人在用MVC4+EF5+MYSQL搭建自己的博客.地址:www.seesharply.com;遇到一个问题,就是采用ef的codefirst模式来编写程序,我们一般会在程序开发初期直接在glob ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(1)-前言与目录(持续更新中...)
开发工具:VS2015(2012以上)+SQL2008R2以上数据库 您可以有偿获取一份最新源码联系QQ:729994997 价格 666RMB 升级后界面效果如下: 任务调度系统界面 http: ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(63)-Excel导入和导出-自定义表模导入
系列目录 前言 上一节使用了LinqToExcel和CloseXML对Excel表进行导入和导出的简单操作,大家可以跳转到上一节查看: ASP.NET MVC5+EF6+EasyUI 后台管理系统(6 ...
随机推荐
- 在.NET CORE中使用配置文件:对 ConfigurationBuilder 的使用说明
示例:ASP.NET MVC 使用示例: 如何覆写默认行为?如取消热更新支持,方法如下: 示例:控制台 使用应用程序参数 使用键值对枚举(这里以字典来说明) 使用JSON文件 注册配置文件中的某一个段 ...
- AppBoxFuture: 123挨个站-数据按序存储
最近几天在优化存储的编码规则,顺带把之前设计了但未实现的倒排序一并实现了.由于所有数据(元数据.实体.索引等)都映射至RocksDB的Key-Value存储,所以必须扩展RocksDB的自定义比较 ...
- 夯实Java基础(十)——抽象类和接口
转载自:http://cmsblogs.com/ 该博主的网站上干货非常!非常!非常多(说三遍),强烈推荐大家前去学习. 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法 抽象类与接口是 ...
- java中的异常 try catch
1.学习异常的原因? 如果没有异常处理机制,那么程序的一点小问题,都会导致[程序终止运行].实际开发中显然是不可能的,所以异常对于程序来说是非常重要的. 2.处理异常的方式: A ...
- 机器学习tips
1 为什么随机梯度下降法能work? https://www.zhihu.com/question/27012077中回答者李文哲的解释 2 随机梯度下降法的好处? (1)加快训练速度(2)噪音可 ...
- 如何以python风格高逼格的改成购物车逻辑
之前有一篇博文写到关于购物车的业务逻辑,分别运用cookie和redis存储未登录和登录用户的购物车数据,虽然已经很好的完成了业务逻辑,但是会发现代码的冗余很严重,也不够具有python特色,今天就让 ...
- springboot搭建通用mapper
对于搭建一个小项目自己测试玩如果采用传统的SSM框架配置起来太过于繁琐,使用springboot简化配置再搭配通用mapper简直不要太方便,话不多说,直接上代码. 首先是pom文件,直接去sprin ...
- win server 2008搭建域环境
0x00 简介 1.域控:win server 2008 2.域内服务器:win server 2008.win server 2003 3.域内PC:win7 x64.win7 x32.win xp ...
- ASP.NET Core 2.2 : 二十六. 应用JWT进行用户认证
本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新方案(ASP.NET Core 系列目录) 一.什么是JWT? JWT(json web token ...
- Visual Studio 2019 远程调试工具(Remote Debugger)使用方法
目录 0.Visual Studio 2019 远程调试工具使用场景 1.Visual Studio 2019 远程调试工具下载地址: 2.Visual Studio 2019 远程调试工具-安装及运 ...