.NET Core 中采用独立数据库的SAAS(多租户)方法

介绍

多租户是指一种软件架构,其中软件的单个实例在服务器上运行并为多个租户提供服务。

在基于 SAAS 的平台中,租户是指使用该平台开展业务运营的客户。每个租户都拥有独立的数据、用户帐户和配置设置,并且与其他租户隔离。

多租户允许有效利用资源,因为同一个软件实例可以为多个客户提供服务,从而减少了为每个客户提供单独服务器和基础设施的需求。

它还可以更轻松地进行维护和更新,因为可以对软件的单个实例进行更改,而不必为每个客户更新多个实例。

不同的实施方案

我们可以将多租户实现分为三种不同的方法。

单一数据库

所有租户共享一个数据库实例和架构。这种方法最节省资源,跨租户的数据分析也变得更加简单。这是一种经济高效的方法,并简化了管理。

但这种方法的安全性成为一个关键问题;随着租户数量的增加,出现性能瓶颈的可能性也会增大。数据法规的合规性,尤其是当租户在不同地区运营且数据保护法规各异时,管理起来会变得复杂且具有挑战性。

具有单独模式的单一数据库

所有租户将共享单个数据库,但每个租户将拥有各自的架构。这种方法比单独的数据库更节省资源,同时仍然提供高级别的隔离和安全性。管理单个数据库可以降低运营成本,例如备份和更新等管理任务。

然而,在资源争用程度较高的场景中,例如跨不同模式的频繁读写操作,可能会出现数据争用和性能下降。

独立数据库

每个租户都将拥有自己的数据库,并且应用程序将有一个通用数据库来存储特定于租户的信息。多个数据库可提供更强的安全性和隔离性,从而降低租户之间数据泄露和未经授权访问的风险。

可扩展性得到增强,因为每个租户的数据库都可以根据各自的需求和使用模式独立扩展。

然而,管理多个数据库会增加管理任务的复杂性,可能会出现同步挑战,导致不同数据库之间数据不一致。此外,存储、处理和基础设施成本方面的资源消耗也会更高。

今天,在本博客中,我们将学习如何在 .Net Core 应用程序中实现多租户的单独数据库方法以及如何向 DbContext 提供动态连接字符串。

我将给出一个应用程序的演示,其中我将采用两个 DbContext 类。

        1、一个用于主数据库的 DB Context,它将存储与租户相关的内容,例如租户详细信息、租户用户登录等。

        2、每个租户数据库的第二个数据库上下文,我们将根据 TenantId 连接该数据库

我们将添加一个名为 x-tenant-id 的请求标头,其中将包含我们想要获取数据的租户 ID。

步骤 1

创建租户 Pod 表。我们可以使用此表存储数据库的租户连接字符串。如果我们不想将连接字符串直接存储在数据库中,可以使用 Azure Key Vault 或 AWS Secret Manager,并在该数据库中存储 Secret 名称。

// Tenant.cs
[Table("tenants")]
public class Tenant
{
[Key]
public Guid Id { get; protected set; }
public string Name { get; protected set; }
public Tenant(Guid id, string name)
{
Id = id;
Name = name;
}
}

// TenantPod.cs
[Table("tenant_pods")]
public class TenantPod
{
[Key]
public Guid Id { get; protected set; }
public Guid TenantId { get; protected set; }
public string ConnectionString { get; protected set; }
[ForeignKey(nameof(TenantId))]
public virtual Tenant Tenant { get; protected set; }
public TenantPod(Guid id, Guid tenantId, string connectionString)
{
Id = id;
TenantId = tenantId;
ConnectionString = connectionString;
}
}

步骤2

创建一个方法,根据请求标头获取连接字符串。

// TenantsService.cs
public interface ITenantsService
{
string GetConnectionByTenant();
}

public class TenantsService: ITenantsService
{
private readonly HttpContext? _httpContext;
public TenantsService(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public string GetConnectionByTenant()
{
var tenantId = Guid.Empty;
try
{
if (_httpContext == null)
throw new Exception("Tenant Not Found");

            var clientId = _httpContext.Request.Headers["x-tenant_id"];
if (string.IsNullOrEmpty(clientId))
{
throw new Exception("Tenant Request Header is missing.");
}
Guid.TryParse(clientId.ToString(), out tenantId);
// Logic to get DB connection based on tenant
// Dummy Connection string Her you should have your logic to get connection string and return it
return "Server=127.0.0.1;Database=multiDemoMain;Port=5432;User Id=postgres;Password=123456";
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}
}

步骤3

在Db Context中,添加逻辑以使用上述方法,覆盖DBContext类的OnConfiguring方法。

// MultiTenantDbContext.cs
public class MultiTenantDbContext : DbContext
{
private readonly ITenantsService _tenantsService;
public MultiTenantDbContext(DbContextOptions<MultiTenantDbContext> options, ITenantsService tenantsService) :
base(options)
{
_tenantsService = tenantsService;
}
public DbSet<Tenant> Tenants { get; set; }
public DbSet<Document> Documents { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connectionString = _tenantsService.GetConnectionByTenant();
if (string.IsNullOrEmpty(connectionString))
{
throw new Exception("Tenant Connection Not found");
}
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.UseNpgsql(connectionString, b =>
{
b.SetPostgresVersion(new Version(9, 6));
b.EnableRetryOnFailure(10, TimeSpan.FromSeconds(30), null);
b.CommandTimeout(300);
});
optionsBuilder.UseNpgsql(connectionString).UseSnakeCaseNamingConvention();
}
}

就是这样。现在,您可以在存储库或服务层中使用此数据库上下文,并且此 DbContext 将根据传递的 TenantId 获取连接字符串。我们需要确保每个 API 的请求标头中都有一个租户。

注意:在本例中,我有一个在两个数据库中都重复的租户表,但要使用约束,我们需要复制该表,并且应该有逻辑来同步数据库中的数据。此外,我们可以实现 Redis 缓存或内存缓存服务来从内存中获取连接字符串,从而加快速度。在我的下一篇文章中,我们将学习如何在 .Net Core 中使用内存缓存和 Redis 缓存。

结论

在本文中,我们了解了多租户及其不同的实现方式。此外,我们还探索了如何基于 TenantId 动态连接数据库。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94299.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运维日常工作100条

这是一份非常详细和实用的“运维日常工作100条”清单。它涵盖了从日常巡检、变更管理、故障处理到安全、优化和文档等运维工作的方方面面,可以作为运维工程师的日常工作指南和检查清单。 运维日常工作100条 一、日常巡检与监控 (20条) 检查核心监控大盘:查看整体业务健康状态…

OpenHarmony子系统介绍

OpenHarmony子系统OpenHarmony子系统1. AI业务子系统2. 方舟运行时子系统3. ArkUI框架子系统4. DFX子系统5. DeviceProfile子系统6. XTS子系统7. 上传下载子系统8. 主题框架子系统9. 事件通知子系统10. 位置服务子系统11. 元能力子系统12. 全局资源调度子系统13. 全球化子系统1…

博士招生 | 英国谢菲尔德大学 招收计算机博士

内容源自“图灵学术博研社”gongzhonghao学校简介谢菲尔德大学&#xff08;The University of Sheffield&#xff09;是英国久负盛名的公立研究型大学&#xff0c;也是罗素集团成员之一。在 2026 年 QS 世界大学排名中&#xff0c;谢菲尔德大学位列第92位&#xff0c;其中计算机…

如何理解面向过程和面向对象,举例说明一下?

面向过程和面向对象是两种不同的编程思想&#xff0c;核心区别在于解决问题的视角不同&#xff1a;前者关注 “步骤和过程”&#xff0c;后者关注 “对象和交互”。面向过程的核心思想是把问题拆解成一系列步骤&#xff0c;通过函数实现每个步骤&#xff0c;然后按顺序调用这些…

深入了解评估与微调中使用的Graders:原理、实现与最佳实践

深入了解评估与微调中使用的Graders 在模型评估与微调&#xff08;Fine-tuning&#xff09;过程中&#xff0c;Graders&#xff08;评分器&#xff09;是衡量模型输出与参考答案之间表现的重要工具。本文将系统介绍Grader的类型、技术实现及如何在实际项目中融入稳定且高质量的…

行缓存(line buffer)在图像卷积中的工作方式

上面这张图配合文字&#xff0c;展示了行缓存&#xff08;line buffer&#xff09;在图像卷积中的工作方式&#xff1a;上半部分是一个按行扫描输入的图像块&#xff08;示例为 99&#xff0c;编号 1–81&#xff09;。 蓝色表示已被写入行缓存并按队列等待的数据&#xff0c;绿…

【数据分享】中国371个城市的坡度矢量数据和excel数据

今天要说明数据就是中国371个城市的坡度矢量数据和excel数据。数据介绍在城市发展的进程中&#xff0c;地形地貌始终是影响规划决策的关键因素&#xff0c;而坡度作为表征地表倾斜程度的核心指标&#xff0c;更是贯穿于城市建设、生态保护等诸多环节。本文将全面解读中国 371 个…

《WINDOWS 环境下32位汇编语言程序设计》第7章 图形操作(1)

图形设备接口GDI&#xff08;Graphics Device Interface&#xff09;是Win32的一个重要组成部分&#xff0c;其作用是允许Windows的应用程序将图形输出到计算机屏幕、打印机或其他输出设备上。GDI实际上是一个函数库&#xff0c;包括直线、画图和字体处理等数百个函数。7.1 GDI…

数据结构-HashMap

在 Java 键值对&#xff08;Key-Value&#xff09;集合中&#xff0c;HashMap 是使用频率最高的实现类之一&#xff0c;凭借高效的查找、插入性能&#xff0c;成为日常开发的 “利器”。本文将从 HashMap 的底层原理、核心特点、常用方法到遍历方式、使用注意事项&#xff0c;进…

[系统架构设计师]安全架构设计理论与实践(十八)

[系统架构设计师]安全架构设计理论与实践&#xff08;十八&#xff09; 一.信息安全面临的威胁 1.信息系统安全威胁的来源 物理环境&#xff0c;通信链路&#xff0c;网络系统&#xff0c;操作系统&#xff0c;应用系统&#xff0c;管理系统 2.网络与信息安全风险类别 风险类别…

AI适老服务暖人心:AI适老机顶盒破数字鸿沟、毫米波雷达护独居安全,银发生活新保障

银发经济领域长期受限于 “专业照护资源稀缺”“老年人数字适应能力弱”“独居老人安全隐患多” 的困境&#xff0c;而 AI 技术的适老化改造&#xff0c;正让银发服务从 “被动保障” 转向 “主动关怀”&#xff0c;既能帮老年人跨越数字鸿沟&#xff0c;又能为独居老人筑起安全…

Linux应用软件编程---网络编程1(目的、网络协议、网络配置、UDP编程流程)

Linux下的网络编程一、目的不同主机&#xff0c;进程间通信。二、解决的问题1. 主机与主机之间物理层面必须互联互通。 2. 进程与进程在软件层面必须互联互通。物理层面的互联互通流程图如下&#xff1a;其中&#xff1a;IP地址&#xff1a;计算机的软件地址&#xff0c;用来标…

常见开源协议详解:哪些行为被允许?哪些被限制?

常见开源协议详解&#xff1a;哪些行为被允许&#xff1f;哪些被限制&#xff1f; 开源世界的魅力在于共享与合作&#xff0c;但不同的开源协议对分发、修改、再发布以及宣传/推广有不同的要求和限制。很多开发者在 fork 项目、改 README、放到自己仓库并在自媒体传播 时&…

服务器硬盘进行分区和挂载

查看服务器上的硬盘&#xff1a;lsblk -d -o NAME,SIZE,MODEL可以看到我的硬盘是除了vda系统盘以外&#xff0c;还有个vdb。我们查看一下分区&#xff1a;lsblk可以看到&#xff1a;vdb 1T disk &#xff08;底下没有分区&#xff0c;也没有挂载&#xff09;我们想要用起来这…

【C初阶】数据在内存中的存储

目录 1. 整数在内存中的存储 2. 大小端字节序 2.1 什么是大小端&#xff1f; 2.2 为什么有大小端&#xff1f; 2.3 练习 2.3.1 练习1 2.3.2 练习2 2.3.3 练习3 2.3.4 练习4 2.3.5 练习5 2.3.6 练习6 3. 浮点数在内存中的存储 3.1 浮点数存储的过程 3.2 浮点数的取…

AI 自动化编程 trae 体验2 帮我分析一个项目

总结&#xff1a; 接手一个项目可以让trae 帮忙分析 上次讲到trae在处理组件引入的时候&#xff0c;经常会碰到版本问题&#xff0c;分析引入了互联网上非本版本或者有bug的代码。主要依赖互联网的资源库。 但是分析一个项目应该是没问题。 这次表现非常好&#xff0c;接手一个…

VMware虚拟机中CentOS 7 报错 ping: www.xxx.com: Name or service not known

1:主要原因是网络配置的问题 2:其实就是下面三张图片中的,物理机虚拟网卡 vmware8 和虚拟机网络编辑器&#xff0c;如果设置静态IP 就是这三个地方的问题最简单的解决办法第一步&#xff1a;还原虚拟机网络点击确认后 ** 第二步给自己的虚拟机设置网络连接方式 选择NAT模式连接…

Java面试-自动装箱与拆箱机制解析

&#x1f44b; 欢迎阅读《Java面试200问》系列博客&#xff01; &#x1f680;大家好&#xff0c;我是Jinkxs&#xff0c;一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后&#xff0c;我深知面试不仅是对知识的考察&#xff0c;更是对理解深度与表达能力的…

《VMware 安装 CentOS 7.9 虚拟机详细教程(含图解步骤)》

目录1.安装前准备1.1 准备VMware软件1.1.1 方式一1.1.2 方式二1.2 准备centos7.9镜像1.2.1 方式一1.2.2 方式二2.安装centos7.91.安装前准备 1.1 准备VMware软件 VMware需要的激活码百度直接搜索vmware workstation17激活码就可以搜索到 1.1.1 方式一 这种方式需要注册官网的…

新能源知识库(84)什么是IEC白皮书

IEC白皮书是由国际电工委员会&#xff08;IEC&#xff09;发布的战略性技术文件&#xff0c;旨在针对新兴技术和社会发展趋势&#xff0c;提出标准化需求和发展路径&#xff0c;为全球产业提供前瞻性指导。在新能源领域&#xff0c;IEC白皮书是推动技术创新、产业协同和国际规则…