Virtual Navigation Properties and Multi-Tenancy

后端 未结 2 619
失恋的感觉
失恋的感觉 2020-12-18 10:19

I have a standard DbContext with code like the following:

 public DbSet Interests { get; set; }
 public DbSet Users         


        
相关标签:
2条回答
  • 2020-12-18 10:23

    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.

    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[]).

    For each of the properties you found by doing this, you'll have to write a Where() clause that filters those properties.

    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 (e.g. ContactModel.AddressID) that get posted to your repository (for example from an MVC site) are accessible for the currently logged on tenant. 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.

    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.

    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.

    0 讨论(0)
  • 2020-12-18 10:45

    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...):

    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
    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)
    3. EF5 model is mapped to the VIEW, not the 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.

    0 讨论(0)
提交回复
热议问题