使用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 ...
随机推荐
- PythonDay03
## 第三章 ### 今日内容 1.整型 2.布尔值 3.字符串 索引 切片 步长 字符串的方法 4.for循环 ### 1.整型 - python3:全部是整形- python2:整形,长 ...
- 又拍云叶靖:OpenResty 在又拍云存储中的应用
2019 年 7 月 6 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·上海站,又拍云平台开发部负责人叶靖在活动上做了<OpenRest ...
- 使用webstorm搭建vue-cli项目
前言 随着vue在前端不断的壮大,越来越多的前端工程师使用vue了,作为大型项目的开发,vue-cli是不二之选,所以这篇博客是为搭建vue-cli所写,想要搭建vue-cli项目就必须先有git,n ...
- LeetCode——372. Super Pow
题目链接:https://leetcode.com/problems/super-pow/description/ Your task is to calculate ab mod 1337 wher ...
- android ——悬浮按钮及可交互提示
一.悬浮按钮 FloatingActionButton是Design Support中的一个控件,它会默认colorAccent作为按钮的颜色,还可以给按钮一个图标. 这是没有图标的,这是有图标的. ...
- input默认值设置
在input框里我们可以设置 一些默认值,在点击之后input之后就消失了 <input id="_le_name" type="text" onFocu ...
- Java学习|多线程学习笔记
什么是线程? 可以理解为进程中独立运行的字任务. 使用多线程: 1.继承Thread类:从源码可以看到,Thread累实现了Runnable接口. 如果多次调用st ...
- JVM面试十问
1. JVM运行时划分哪几个区域?哪些区域是线程共享的?哪些区域是线程独占的? JVM运行时一共划分:程序计数器.虚拟机栈.堆.本地方法栈.方法区. 线程共享的数据区域:堆.方法区. 线程独享的数据区 ...
- 【原创】微信小程序支付java后台案例(公众号支付同适用)(签名错误问题)
前言 1.微信小程序支付官方接口文档:[点击查看微信开放平台api开发文档]2.遇到的坑:预支付统一下单签名结果返回[签名错误]失败,建议用官方[签名验证工具]检查签名是否存在问题.3.遇到的坑:签名 ...
- 纯数据结构Java实现(1/11)(动态数组)
我怕说这部分内容太简单后,突然蹦出来一个大佬把我虐到哭,还是悠着点,踏实写 大致内容有: 增删改查,泛型支持,扩容支持,复杂度分析.(铺垫: Java语言中的数组) 基础铺垫 其实没啥好介绍的,顺序存 ...