[英]Virtual Navigation Properties and Multi-Tenancy
我有一个标准的DbContext
,其代码如下所示:
public DbSet<Interest> Interests { get; set; }
public DbSet<User> Users { get; set; }
我最近通过创建一个包含以下内容的TenantContext
实现了多租户:
private readonly DbContext _dbContext;
private readonly Tenant _tenant;
public TenantContext(Tenant tenant)
: base("name=DefaultConnection") {
this._tenant = tenant;
this._dbContext = new DbContext();
}
public IQueryable<User> Users { get { return FilterTenant(_dbContext.Users); } }
public IQueryable<Interest> Interests { get { return FilterTenant(_dbContext.Interests); } }
private IQueryable<T> FilterTenant<T>(IQueryable<T> values) where T : class, ITenantData
{
return values.Where(x => x.TenantId == _tenant.TenantId);
}
到目前为止,这一直很好。 每当我的任何服务创建新的TenantContext时,都会直接通过该FilterTenant
方法过滤该上下文之外的所有查询,以确保我仅返回与租户相关的实体。
我遇到的问题是我对导航属性的使用没有考虑到这一点:
using (var db = CreateContext()) // new TenantContext
{
return db.Users.
Include(u => u.Interests).FirstOrDefault(s => s.UserId == userId);
}
此查询拉起特定于租户的Users
,但是Include()
语句仅拉动该用户的Interests
-跨所有租户。 因此,如果一个用户在多个租户中都拥有兴趣,则可以通过上述查询获得该用户的所有兴趣。
我的用户模型具有以下内容:
public int UserId { get; set; }
public int TenantId { get; set; }
public virtual ICollection<Interest> Interests { get; set; }
有什么方法可以修改这些导航属性以执行特定于租户的查询? 还是我应该去掉所有导航属性以支持手写代码?
第二个选项使我感到恐惧,因为很多查询都嵌套了“包含”。 这里的任何输入都是很棒的。
据我所知,没有其他方法可以使用反射或手动查询属性。
因此,在您的IQueryable<T> FilterTenant<T>(IQueryable<T> values)
方法中,您必须检查T
类型的实现ITenantData
接口的属性。
然后您仍然不在那里,因为您的根实体(本例中为User
)的属性可能是实体本身,或者是实体列表(请考虑Invoice.InvoiceLines[].Item.Categories[]
)。
对于执行此操作发现的每个属性,都必须编写一个Where()
子句以过滤这些属性 。
或者,您可以按属性手动对其进行编码 。
这些检查至少应在创建和编辑实体时进行。 您需要检查由ID属性引用的导航属性(例如ContactModel.AddressID
),该属性已发布到您的存储库(例如,从MVC站点),对于当前登录的租户是否可访问。 这是您的批量分配保护,可确保恶意用户无法发布请求,否则该请求仅通过随机发布一个随机请求即可将其拥有权限(他正在创建或编辑的Contact
的实体链接到另一租户的一个Address
或已知的AddressID
。
如果您信任此系统,则在读取时只需检查根实体的TenantID,因为在创建和更新时进行了检查,如果可以访问根实体,则所有子实体都可供租户访问。
由于您的描述,您确实需要过滤子实体。 使用此处介绍的技术对示例进行手动编码的示例:
public class UserRepository
{
// ctor injects _dbContext and _tenantId
public IQueryable<User> GetUsers()
{
var user = _dbContext.Users.Where(u => u.TenantId == _tenantId)
.Select(u => new User
{
Interests = u.Interests.Where(u =>
u.TenantId == _tenantId),
Other = u.Other,
};
}
}
}
但是正如您所看到的,您必须像这样映射User
每个属性。
只是想提供一种实现多租户的替代方法,该方法在当前项目中使用EF5和SQL 2012确实非常有效。基本设计是(这里是我的责任...):
WHERE (ClientSid = SUSER_SID())
在表上的直接选择,但不选择ClientSid(有效地公开表的接口) 差不多就可以了-尽管分享可能会有用。 我知道这不是您问题的直接答案,但是这导致C#区域中的自定义代码基本上为零。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.