[英]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确实非常有效。基本设计是(这里是我的责任...):
WHERE (ClientSid = SUSER_SID())
but doesn't select the ClientSid (effectively exposing the interface of the table) WHERE (ClientSid = SUSER_SID())
在表上的直接选择,但不选择ClientSid(有效地公开表的接口) 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.