繁体   English   中英

虚拟导航属性和多租户

[英]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确实非常有效。基本设计是(这里是我的责任...):

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

差不多就可以了-尽管分享可能会有用。 我知道这不是您问题的直接答案,但是这导致C#区域中的自定义代码基本上为零。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM