我的组织需要一个共享数据库,共享模式多租户数据库。我们将基于TenantId进行查询。我们将有很少的租户(少于10人),并且所有人都将共享相同的数据库模式,而不支持特定于租户的更改或功能。租户元数据将存储在内存中,而不是存储在DB (静态成员)中。
这意味着所有实体现在都需要一个TenantId,而DbContext需要知道在默认情况下对此进行筛选。
除非有更明智的方法,否则TenantId可能会由头值或起始域来标识。
我见过各种利用拦截器的示例,但还没有看到TenantId实现上的明确示例。
我们需要解决的问题:
tenantId修改所有的方法签名)User,以某种方式将它们与租户关联起来。我认为最大的问题是4-修改DbContext。
我喜欢本文如何利用RLS,但我不确定如何以代码优先(首先,dbContext )的方式处理这个问题:
我要说的是,我要寻找的是一种方法--考虑到性能--有选择地使用DbContext查询tenantId隔离的资源,而不需要使用、、等。
更新--我找到了一些选项,但我不确定每个选项的优缺点,或者是否有一些“更好”的方法。我对备选方案的评价可以归结为:
方法A
这似乎是“昂贵的”,因为每次我们新的dbContext,我们必须重新初始化过滤器:
首先,我设置了我的租户和接口:
public static class Tenant {
public static int TenantA {
get { return 1; }
}
public static int TenantB
{
get { return 2; }
}
}
public interface ITenantEntity {
int TenantId { get; set; }
}我在任何实体上实现这一接口:
public class Photo : ITenantEntity
{
public Photo()
{
DateProcessed = (DateTime) SqlDateTime.MinValue;
}
[Key]
public int PhotoId { get; set; }
[Required]
public int TenantId { get; set; }
}然后我更新我的DbContext实现:
public AppContext(): base("name=ProductionConnection")
{
Init();
}
protected internal virtual void Init()
{
this.InitializeDynamicFilters();
}
int? _currentTenantId = null;
public void SetTenantId(int? tenantId)
{
_currentTenantId = tenantId;
this.SetFilterScopedParameterValue("TenantEntity", "tenantId", _currentTenantId);
this.SetFilterGlobalParameterValue("TenantEntity", "tenantId", _currentTenantId);
var test = this.GetFilterParameterValue("TenantEntity", "tenantId");
}
public override int SaveChanges()
{
var createdEntries = GetCreatedEntries().ToList();
if (createdEntries.Any())
{
foreach (var createdEntry in createdEntries)
{
var isTenantEntity = createdEntry.Entity as ITenantEntity;
if (isTenantEntity != null && _currentTenantId != null)
{
isTenantEntity.TenantId = _currentTenantId.Value;
}
else
{
throw new InvalidOperationException("Tenant Id Not Specified");
}
}
}
}
private IEnumerable<DbEntityEntry> GetCreatedEntries()
{
var createdEntries = ChangeTracker.Entries().Where(V => EntityState.Added.HasFlag(V.State));
return createdEntries;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Filter("TenantEntity", (ITenantEntity tenantEntity, int? tenantId) => tenantEntity.TenantId == tenantId.Value, () => null);
base.OnModelCreating(modelBuilder);
}最后,在我对DbContext的调用中,我使用以下内容:
using (var db = new AppContext())
{
db.SetTenantId(someValueDeterminedElsewhere);
}我对此有一个问题,因为我在大约一百万个地方更新了我的AppContext (有些服务方法需要它,有些则不需要)--所以这会使我的代码有点臃肿。还有关于租户确定的问题--我是否传入HttpContext,是否强制我的控制器将TenantId传递到所有服务方法调用,如何处理没有原始域(webjob调用等)的情况。
方法B
在此发现:http://howtoprogram.eu/question/n-a,28158
看起来很相似,但很简单:
public interface IMultiTenantEntity {
int TenantID { get; set; }
}
public partial class YourEntity : IMultiTenantEntity {}
public partial class YourContext : DbContext
{
private int _tenantId;
public override int SaveChanges() {
var addedEntities = this.ChangeTracker.Entries().Where(c => c.State == EntityState.Added)
.Select(c => c.Entity).OfType<IMultiTenantEntity>();
foreach (var entity in addedEntities) {
entity.TenantID = _tenantId;
}
return base.SaveChanges();
}
public IQueryable<Code> TenantCodes => this.Codes.Where(c => c.TenantID == _tenantId);
}
public IQueryable<YourEntity> TenantYourEntities => this.YourEntities.Where(c => c.TenantID == _tenantId);虽然这看起来只是一个愚蠢的版本的A有同样的关切。
我认为,到目前为止,必须有一个成熟的、明智的配置/体系结构来满足这一需求。我们该怎么做?
发布于 2016-11-07 16:05:00
我想建议以下方法: 1.为包含核心业务数据的每个表创建一个列,该列的名称为承租者ID,这是任何映射表所不需要的。
IQueryable的扩展方法。该方法可以是dbset的扩展,这样任何人只要编写filter子句,就可以调用这个扩展方法,后面跟着谓词。这将使开发人员更容易编写代码,而不必担心租户ID筛选器。这个特定的方法将根据执行此查询的租户上下文为承租者ID列应用筛选条件的代码。样品 ctx.TenantFilter().Where(....)
发布于 2019-07-12 14:01:15
问题是关于EF,但我认为值得一提的是EF核心。在EF核心中,您可以使用全局查询过滤器
这些筛选器将自动应用于涉及这些实体类型的任何LINQ查询,包括间接引用的实体类型,例如通过使用包含或直接导航属性引用。
举个例子:
public class Blog
{
private string _tenantId;
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public bool IsDeleted { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}发布于 2016-11-15 17:39:05
我认为最大的问题是4-修改DbContext。
不要修改上下文..。
您不应该将租户过滤代码与您的业务代码混合使用。
我认为您所需要的只是一个存储库,返回过滤后的数据
这个存储库将根据从TenantIdProvider获得的Id返回过滤后的数据。
那么,你的服务就不需要知道任何关于租户的事情
using System;
using System.Data.Entity;
using System.Linq;
namespace SqlServerDatabaseBackup
{
public class Table
{
public int TenantId { get; set; }
public int TableId { get; set; }
}
public interface ITentantIdProvider
{
int TenantId();
}
public class TenantRepository : ITenantRepositoty
{
private int tenantId;
private ITentantIdProvider _tentantIdProvider;
private TenantContext context = new TenantContext(); //You can abstract this if you want
private DbSet<Table> filteredTables;
public IQueryable<Table> Tables
{
get
{
return filteredTables.Where(t => t.TenantId == tenantId);
}
}
public TenantRepository(ITentantIdProvider tentantIdProvider)
{
_tentantIdProvider = tentantIdProvider;
tenantId = _tentantIdProvider.TenantId();
filteredTables = context.Tables;
}
public Table Find(int id)
{
return filteredTables.Find(id);
}
}
public interface ITenantRepositoty
{
IQueryable<Table> Tables { get; }
Table Find(int id);
}
public class TenantContext : DbContext
{
public DbSet<Table> Tables { get; set; }
}
public interface IService
{
void DoWork();
}
public class Service : IService
{
private ITenantRepositoty _tenantRepositoty;
public Service(ITenantRepositoty tenantRepositoty)
{
_tenantRepositoty = tenantRepositoty;
}
public void DoWork()
{
_tenantRepositoty.Tables.ToList();//These are filtered records
}
}
}https://stackoverflow.com/questions/40441204
复制相似问题