我正在尝试构建一个 DbContext 基类,它自动实现对租户感知、审计日志等的支持,以便我的团队可以继承它,添加他们的实体,并且只需很少的手动工作,就可以拥有一个可操作的租户强制和托管数据库上下文。这是为了减少改造中的工作量和定制程度,使其稍微不那么疯狂(我们有大约 30 多个服务,每个服务都有自己的数据库上下文)。
我有一个类似这样的界面
ITenantAwareEntity
:
public interface ITenantAwareEntity
{
/// <summary>
/// Specifies the customer that this record belongs to.
/// </summary>
Guid? CustomerId { get; set; }
}
我构建了一个
SaveChangesInterceptor
,以确保在使用注入的 CustomerId
保存之前始终填充 IContextProvider
,该注入提供当前 DbContext 信息,例如当前用户、当前客户等。
在查询方面,我尝试使用全局过滤器强制租赁,该过滤器使用前面提到的
CustomerId
添加了 IContextProvider
的过滤器。有很多关于如何做到这一点的例子,但它们看起来都与我的实现有很大不同,我正在绞尽脑汁地思考如何使这项工作发挥作用。 Lambda 表达式显然是我的克星。
我注入 DbContext 的上下文提供程序(并公开为受保护的 get/私有设置属性)有一个名为
IsServiceContext
的属性,它指示使用 DbContext 的身份不属于特定租户,而是一个我不想应用全局过滤器的服务帐户,因为它可能一次管理多个租户的记录。
我完全不知道如何编写一个 lambda 表达式,其基本内容如下:
IContextProvider
中定义的服务帐户,则不应用过滤器。CustomerId
中 IContextProvider
属性要求的任何结果集。我如何编写它,以便我可以将其应用于实现
ITenantAwareEntity
的所有实体,并在运行时为每个数据库操作动态评估它?
EF Core 允许在查询过滤器中仅使用 DbContext 的属性。所以定义它们:
public class MyDbContext : DbConext
{
...
protected bool IsServiceUser => _contextProvider.IsServiceUser;
protected Guid? CustomerId => _contextProvider.CustomerId;
...
}
无法动态更改查询过滤器 - 它是在模型中定义的,以后无法更改。将
IsServiceUser
和 CustomerId
组合在一个过滤器中:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
... // configuring classes
modelBuilder.Entity<Some>(e => IsServiceUser || CustomerId == e.CustomerId);
}
您还可以使用以下自定义扩展 ApplyQueryFilter 为所有实体定义查询过滤器:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
... // configuring classes
modelBuilder.ApplyQueryFilter<ITenantAwareEntity>(e => IsServiceUser || CustomerId == e.CustomerId);
}