本文介绍了虚拟导航属性和多租户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  public DbSet< Interest>兴趣{get;组; } 
public DbSet< User>用户{get;组;

我最近通过创建一个 TenantContext 包含以下内容:

  private readonly DbContext _dbContext; 
私人只读租户_tenant;

public TenantContext(租户)
:base(name = DefaultConnection){
this._tenant = tenant;
this._dbContext = new DbContext();
}

public IQueryable< User>用户{get {return FilterTenant(_dbContext.Users); }}
public IQueryable< Interest>兴趣{get {return FilterTenant(_dbContext.Interests); }}


private IQueryable< T> FilterTenant< T(IQueryable T value)其中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);
}

此查询提取租户特定的 / code>,但是那个 Include()语句只为该用户提供兴趣所有租户。因此,如果用户对多个租户有兴趣,我可以通过上述查询获取所有用户的兴趣。



我的用户模型有以下内容:

  public int UserId {get;组; } 
public int TenantId {get;组; }
public virtual ICollection< Interest>兴趣{get;组;

有什么办法可以修改这些导航属性来执行特定于租户的查询?还是应该去掉所有的导航属性以支持手写代码?



第二个选项吓倒我,因为很多查询都嵌套了Includes。

解决方案

据我所知,没有其他方法可以使用反射或查询



所以在你的 IQueryable< T> FilterTenant< T>(IQueryable< T>值)方法,您必须检查您的类型 T ,以实现您的 ITenantData 界面。



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



对于通过这样做找到的每个属性,您必须编写一个 Where() clause 。



这些检查至少应在创建和编辑实体时发生。您将需要检查通过ID属性引用的导航属性(例如, ContactModel.AddressID ),可以将其发布到您的存储库(例如从MVC站点)当前登录的租户。这是您的保护,这确保恶意用户无法制定一个请求,否则将其具有权限的实体(一个联系人他正在创建或编辑)链接到另一个租户的一个地址只需发布随机或已知的 AddressID



如果您信任该系统,您只需检查阅读时根实体的TenantID,因为在创建和更新时给予检查,所有子实体都可以访问,如果根实体可访问。



由于您的描述你 do 需要过滤子实体。使用解释的技术手动编写示例的示例发现,:

  public class UserRepository 
{
// ctor注入_dbContext和_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),
其他= u.Other,
};
}
}
}

但是如你所见,你必须映射每一个用户的属性。


I have a standard DbContext with code like the following:

 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:

  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.

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

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.

这篇关于虚拟导航属性和多租户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-19 16:40