简体   繁体   English

虚拟导航属性和多租户

[英]Virtual Navigation Properties and Multi-Tenancy

I have a standard DbContext with code like the following: 我有一个标准的DbContext ,其代码如下所示:

 public DbSet<Interest> Interests { get; set; }
 public DbSet<User> Users { get; set; }

I've recently implemented multi-tenancy by creating a TenantContext that contains the following: 我最近通过创建一个包含以下内容的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);
    }

So far, this has been working great. 到目前为止,这一直很好。 Whenever any of my services creates a new TenantContext, all queries directly off of that context are filtered through this FilterTenant method that guarantees I'm only returning tenant-relevant entities. 每当我的任何服务创建新的TenantContext时,都会直接通过该FilterTenant方法过滤该上下文之外的所有查询,以确保我仅返回与租户相关的实体。

The problem that I'm encountering is my usage of navigation properties that do not take this into account: 我遇到的问题是我对导航属性的使用没有考虑到这一点:

  using (var db = CreateContext())  // new TenantContext
        {
            return db.Users.
                Include(u => u.Interests).FirstOrDefault(s => s.UserId == userId);
        }

This query pulls up the tenant-specific Users , but then the Include() statement pulls in Interests for that user only - but across all tenants. 此查询拉起特定于租户的Users ,但是Include()语句仅拉动该用户的Interests -跨所有租户。 So if a user has Interests across multiple Tenants, I get all of the user's Interests with the above query. 因此,如果一个用户在多个租户中都拥有兴趣,则可以通过上述查询获得该用户的所有兴趣。

My User model has the following: 我的用户模型具有以下内容:

 public int UserId { get; set; }
 public int TenantId { get; set; }
 public virtual ICollection<Interest> Interests { get; set; }

Is there any way that I can somehow modify these navigation properties to perform tenant-specific queries? 有什么方法可以修改这些导航属性以执行特定于租户的查询? Or should I go and tear out all navigation properties in favor of handwritten code? 还是我应该去掉所有导航属性以支持手写代码?

The second option scares me because a lot of queries have nested Includes. 第二个选项使我感到恐惧,因为很多查询都嵌套了“包含”。 Any input here would be fantastic. 这里的任何输入都是很棒的。

As far as I know, there's no other way than to either use reflection or query the properties by hand. 据我所知,没有其他方法可以使用反射或手动查询属性。

So in your IQueryable<T> FilterTenant<T>(IQueryable<T> values) method, you'll have to inspect your type T for properties that implement your ITenantData interface. 因此,在您的IQueryable<T> FilterTenant<T>(IQueryable<T> values)方法中,您必须检查T类型的实现ITenantData接口的属性。

Then you're still not there, as the properties of your root entity ( User in this case) may be entities themselves, or lists of entities (think Invoice.InvoiceLines[].Item.Categories[] ). 然后您仍然不在那里,因为您的根实体(本例中为User )的属性可能是实体本身,或者是实体列表(请考虑Invoice.InvoiceLines[].Item.Categories[] )。

For each of the properties you found by doing this, you'll have to write a Where() clause that filters those properties . 对于执行此操作发现的每个属性,都必须编写一个Where()子句以过滤这些属性

Or you can hand-code it per property . 或者,您可以按属性手动对其进行编码

These checks should at least happen when creating and editing entities. 这些检查至少应在创建和编辑实体时进行。 You'll want to check that navigation properties referenced by an ID property (eg ContactModel.AddressID ) that get posted to your repository (for example from an MVC site) are accessible for the currently logged on tenant. 您需要检查由ID属性引用的导航属性(例如ContactModel.AddressID ),该属性已发布到您的存储库(例如,从MVC站点),对于当前登录的租户是否可访问。 This is your mass assignment protection, which ensures a malicious user can't craft a request that would otherwise link an entity to which he has permissions (a Contact he is creating or editing) to one Address of another tenant, simply by posting a random or known AddressID . 这是您的批量分配保护,可确保恶意用户无法发布请求,否则该请求仅通过随机发布一个随机请求即可将其拥有权限(他正在创建或编辑的Contact的实体链接到另一租户的一个Address或已知的AddressID

If you trust this system, you only have to check the TenantID of the root entity when reading, because given the checks when creating and updating, all child entities are accessible for the tenant if the root entity is accessible. 如果您信任此系统,则在读取时只需检查根实体的TenantID,因为在创建和更新时进行了检查,如果可以访问根实体,则所有子实体都可供租户访问。

Because of your description you do need to filter child entities. 由于您的描述,您确实需要过滤子实体。 An example for hand-coding your example, using the technique explained found here : 使用此处介绍的技术对示例进行手动编码的示例:

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,
                                   };                               
        }
    }
}

But as you see, you'll have to map every property of User like that. 但是正如您所看到的,您必须像这样映射User每个属性。

Just wanted to offer an alternative approach to implementing multi-tenancy, which is working really well in a current project, using EF5 and SQL 2012. Basic design is (bear with me here...): 只是想提供一种实现多租户的替代方法,该方法在当前项目中使用EF5和SQL 2012确实非常有效。基本设计是(这里是我的责任...):

  1. Every table in the database has a column (ClientSid binary, default constraint = SUSER_SID()) and is never queried directly, only ever via a dedicated view 数据库中的每个表都有一列(ClientSid二进制,默认约束= SUSER_SID()),并且从不直接查询,只有通过专用视图才能查询
  2. Each view is a direct select over the table with WHERE (ClientSid = SUSER_SID()) but doesn't select the ClientSid (effectively exposing the interface of the table) 每个视图都是使用WHERE (ClientSid = SUSER_SID())在表上的直接选择,但不选择ClientSid(有效地公开表的接口)
  3. EF5 model is mapped to the VIEW, not the TABLE EF5模型映射到VIEW,而不是TABLE
  4. The connection string is varied based on the context of the tenant (user / client whatever multi-tenant partition requirement may be) 连接字符串根据租户的上下文(用户/客户端,无论多租户分区要求是什么)而变化。

That's pretty much it - though it might be useful to share. 差不多就可以了-尽管分享可能会有用。 I know it's not a direct answer to your question, but this has resulted in basically zero custom code in the C# area. 我知道这不是您问题的直接答案,但是这导致C#区域中的自定义代码基本上为零。

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

相关问题 多租户:每个租户的单个数据库 - Multi-tenancy: Individual database per tenant 实体框架多租户和动态模式 - Entity Framework Multi-Tenancy and dynamic schema 具有过滤的dbContext的多租户Web应用程序 - Multi-tenancy web application with filtered dbContext 具有ASP.NET身份的实体框架多租户 - Entity Framework Multi-Tenancy with ASP.NET Identity 使用 Azure 服务总线重新总线、竞争消费者和多租户 - Rebus with Azure Service Bus, Competing consumers and multi-tenancy 在运行时从子域设置EF ConnectionString以进行多租户设置 - Set EF ConnectionString at runtime from subdomain for multi-tenancy setup EF6 中的多租户具有多个具有相同表的架构 - Multi-tenancy in EF6 with multiple schemas having the same tables 多租户系统中租户控制队列并发 - Control queue concurrency by tenant in multi-tenancy system 将NHibernate繁重的应用程序从具有多个模式的单租户迁移到多租户 - Moving an NHibernate-heavy application from single-tenancy to multi-tenancy with multiple schemas 将现有的ASP.NET Web窗体应用程序转换为多租户 - Convert existing ASP.NET web forms application to multi-tenancy
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM