Marten项目中的多租户文档管理详解
概述
在现代应用开发中,多租户架构是一种常见的设计模式,它允许单个应用实例为多个租户(客户或组织)提供服务,同时保持数据隔离。Marten作为一个.NET平台的文档数据库和事件存储库,提供了强大的多租户支持功能。
多租户基础概念
Marten支持三种多租户模式,通过TenancyStyle
枚举表示:
- 单租户模式(
Single
):默认模式,不启用多租户功能 - 联合租户模式(
Conjoined
):通过租户ID实现多租户 - 分离租户模式(
Separate
):通过独立数据库或模式实现多租户(规划中)
租户会话管理
创建租户会话
Marten通过会话(IQuerySession
, IDocumentSession
)来管理租户范围。创建特定租户的会话非常简单:
// 为"tenant1"租户创建轻量级会话
using (var session = store.LightweightSession("tenant1"))
{
// 存储用户文档
session.Store(new User { UserName = "Bill" });
session.Store(new User { UserName = "Lindsey" });
await session.SaveChangesAsync();
}
查询租户数据
查询操作会自动限定在当前会话的租户范围内:
using (var query = store.QuerySession("tenant1"))
{
var users = query.Query<User>()
.Select(x => x.UserName)
.ToList();
// 只会返回"tenant1"租户下的用户
}
默认租户处理
当启用多租户功能时,Marten会为每个记录关联一个租户标识符。如果没有明确指定租户,Marten会使用默认租户ID(Tenancy.DefaultTenantId
),其值为*DEFAULT*
。
可以通过配置禁用默认租户:
storeOptions.Advanced.DefaultTenantUsageEnabled = false;
禁用后,如果尝试使用默认租户创建会话,Marten会抛出DefaultTenantUsageDisabledException
异常。
多租户查询技巧
自动租户过滤
当使用特定租户的会话查询时,Marten会自动为查询添加租户过滤条件(如果文档类型是多租户的)。
跨租户查询
有时需要查询多个租户的数据,Marten提供了两种方式:
- TenantIsOneOf:查询指定租户列表的数据
// 查询Green和Red租户的数据
var results = await query.Query<Target>()
.Where(x => x.TenantIsOneOf("Green", "Red") && x.Flag)
.OrderBy(x => x.Id)
.Select(x => x.Id)
.ToListAsync();
- AnyTenant:查询所有租户的数据
// 查询所有租户的数据
var results = query.Query<Target>()
.Where(x => x.AnyTenant() && x.Flag)
.OrderBy(x => x.Id)
.Select(x => x.Id)
.ToArray();
租户配置策略
全局配置
可以在存储级别配置租户策略,应用于所有文档:
var store = DocumentStore.For(opts =>
{
opts.Connection("connection_string");
opts.Policies.AllDocumentsAreMultiTenanted();
});
文档级配置
也可以为特定文档类型配置租户策略:
var store = DocumentStore.For(opts =>
{
opts.Connection("connection_string");
opts.Schema.For<User>().MultiTenanted();
opts.Schema.For<Region>().SingleTenanted();
});
租户分区优化(7.26+)
对于使用联合多租户的应用,可以通过PostgreSQL表分区功能提升性能。Marten支持多种分区策略:
- 列表分区(LIST):按租户ID列表分区
storeOptions.Policies.AllDocumentsAreMultiTenantedWithPartitioning(x =>
{
x.ByList()
.AddPartition("t1", "T1")
.AddPartition("t2", "T2");
});
- 哈希分区(HASH):按租户ID哈希值分区
x.ByHash("one", "two", "three");
- 范围分区(RANGE):按租户ID范围分区
x.ByRange()
.AddRange("north_america", "na", "nazzzzzzzzzz")
.AddRange("asia", "a", "azzzzzzzz");
也可以让外部工具管理分区:
x.ByExternallyManagedListPartitions();
单表分区配置
为特定文档类型配置分区:
opts.Schema.For<User>().MultiTenantedWithPartitioning(x =>
{
x.ByExternallyManagedListPartitions();
});
最佳实践
- 明确租户边界:始终确保操作在正确的租户上下文中执行
- 谨慎使用跨租户查询:仅在确实需要时使用,避免意外数据泄露
- 考虑分区策略:根据租户数量和访问模式选择合适的分区方式
- 测试默认租户行为:确保理解并测试默认租户的行为是否符合预期
通过合理利用Marten的多租户功能,可以构建既安全又高效的SaaS应用,确保不同客户数据的严格隔离,同时保持良好的系统性能。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考