虚拟导航属性和多租户

我有一个标准的DbContext ,代码如下:

  public DbSet Interests { get; set; } public DbSet 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 Users { get { return FilterTenant(_dbContext.Users); } } public IQueryable Interests { get { return FilterTenant(_dbContext.Interests); } } private IQueryable FilterTenant(IQueryable 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 Interests { get; set; } 

有什么方法可以以某种方式修改这些导航属性来执行特定于租户的查询? 或者我应该去掉所有导航属性,转而使用手写代码?

第二个选项让我感到害怕,因为很多查询都嵌套了包含。 这里的任何输入都很棒。

据我所知,除了使用reflection或手动查询属性之外别无他法。

因此,在IQueryable FilterTenant(IQueryable values)方法中,您必须检查类型T以获取实现ITenantData接口的属性。

然后你仍然不在那里,因为你的根实体(在这种情况下是User )的属性可能是实体本身,或实体列表(想想Invoice.InvoiceLines[].Item.Categories[] )。

对于通过执行此操作找到的每个属性,您必须编写一个Where()子句来过滤这些属性 。

或者您可以为每个属性手动编码 。

这些检查至少应该在创建和编辑实体时发生。 您需要检查当前登录的租户是否可以访问发布到您的存储库(例如来自MVC站点)的ID属性(例如ContactModel.AddressID )引用的导航属性。 这是您的大规模分配保护,它可以确保恶意用户无法制作请求,否则只需通过发布随机链接将他有权限的实体(他正在创建或编辑的Contact )链接到另一个租户的一个Address或已知的AddressID

如果您信任此系统,则只需在读取时检查根实体的TenantID,因为在创建和更新时进行检查,如果可以访问根实体,则所有子实体都可供租户访问。

由于您的描述,您需要过滤子实体。 使用此处说明的技术手动编写示例的示例:

 public class UserRepository { // ctor injects _dbContext and _tenantId public IQueryable 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.基本设计是(请耐心等待……):

  1. 数据库中的每个表都有一个列(ClientSid二进制文件,默认约束= SUSER_SID()),永远不会直接查询,只能通过专用视图查询
  2. 每个视图都是使用WHERE (ClientSid = SUSER_SID())直接选择表,但不选择ClientSid(有效地暴露表的接口)
  3. EF5模型映射到VIEW,而不是TABLE
  4. 连接字符串根据租户的上下文(用户/客户端,可能是多租户分区要求)而变化

这就是它 – 尽管它可能有用分享。 我知道这不是你的问题的直接答案,但这导致C#领域基本上没有自定义代码。