多組織用戶管理
許多企業營運應用程式的設計目的是要與多個客戶合作。 請務必保護數據,讓客戶數據不會「外泄」或由其他客戶和潛在競爭對手看到。 這些應用程式會分類為「多租使用者」,因為每個客戶都會被視為具有自己數據集的應用程式租使用者。
警告
本文使用本機資料庫,其不需要使用者進行驗證。 實際執行應用程式應該使用可用的最安全驗證流程。 如需已部署測試與實際執行應用程式驗證的詳細資訊,請參閱安全驗證流程。
重要
本檔提供「如目前」的範例和解決方案。這些不是「最佳做法」,而是要考慮「工作作法」。
提示
您可以在 GitHub 上檢視此 範例的原始程式碼
支援多租使用者
在應用程式中實作多租使用者的方法有很多種。 其中一個常見的方法(有時是需求),就是將每個客戶的數據保留在個別的資料庫中。 架構相同,但數據是客戶特定的。 另一種方法是依客戶分割現有資料庫中的數據。 這可以藉由使用數據表中的數據行,或在多個架構中具有每個租使用者的架構的數據表來完成。
方法 | 租用戶的數據行? | 每個租用戶的架構? | 多個資料庫? | EF Core 支援 |
---|---|---|---|---|
歧視性(欄) | 是 | 無 | No | 全域查詢篩選 |
每一租用戶一個資料庫 | No | 無 | Yes | 組態 |
每個租用戶的架構 | No | .是 | No | 不支援 |
針對每個租用戶的資料庫方法,切換至正確的資料庫就如同提供正確的 連接字串 一樣簡單。 當數據儲存在單一資料庫中時, 全域查詢篩選 可用來依租使用者標識碼自動篩選數據列,確保開發人員不會不小心撰寫程式代碼來存取其他客戶的數據。
這些範例應該在大部分的應用程式模型中正常運作,包括控制台、WPF、WinForms 和 ASP.NET Core 應用程式。 Blazor Server 應用程式需要特別考慮。
Blazor Server 應用程式和處理站的生活
在 Blazor 應用程式中使用 Entity Framework Core 的建議模式是註冊 DbContextFactory,然後呼叫它來建立每個作業的新實例DbContext
。 根據預設,處理站是單一 複 本,因此應用程式所有使用者只有一個複本存在。 這通常沒問題,因為雖然共用處理站,但個別 DbContext
實例則不是。
不過,針對多租使用者,連接字串 可能會變更每個使用者。 因為處理站會快取具有相同存留期的組態,這表示所有用戶都必須共用相同的組態。 因此,存留期應該變更為 Scoped
。
Blazor WebAssembly 應用程式中不會發生此問題,因為單一範圍限定於使用者。 另一方面,Blazor Server 應用程式提出了獨特的挑戰。 雖然應用程式是 Web 應用程式,但使用 SignalR 進行即時通訊會「保持運作」。 每個使用者會建立會話,並持續超過初始要求。 每個使用者都應該提供新的處理站,以允許新的設定。 此特殊處理站的存留期已設定範圍,而且會為每個用戶會話建立新的實例。
範例解決方案 (單一資料庫)
可能的解決方法是建立簡單的 ITenantService
服務,以處理使用者目前租用戶的設定。 它會提供回呼,讓程式代碼在租用戶變更時收到通知。 實作(為了清楚起見而省略回呼)看起來可能像這樣:
namespace Common
{
public interface ITenantService
{
string Tenant { get; }
void SetTenant(string tenant);
string[] GetTenants();
event TenantChangedEventHandler OnTenantChanged;
}
}
DbContext
接著,可以管理多租使用者。 方法取決於您的資料庫策略。 如果您要將所有租使用者儲存在單一資料庫中,您可能會使用查詢篩選器。 ITenantService
會透過相依性插入傳遞至建構函式,並用來解析及儲存租使用者標識符。
public ContactContext(
DbContextOptions<ContactContext> opts,
ITenantService service)
: base(opts) => _tenant = service.Tenant;
方法 OnModelCreating
會覆寫以指定查詢篩選條件:
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<MultitenantContact>()
.HasQueryFilter(mt => mt.Tenant == _tenant);
這可確保每個查詢都會在每個要求上篩選至租使用者。 應用程式程式代碼中不需要篩選,因為會自動套用全域篩選。
租使用者提供者,並 DbContextFactory
設定在應用程式啟動時,如下所示,使用 Sqlite 作為範例:
builder.Services.AddDbContextFactory<ContactContext>(
opt => opt.UseSqlite("Data Source=singledb.sqlite"), ServiceLifetime.Scoped);
請注意,服務 存留期 是使用 ServiceLifetime.Scoped
設定的。 這可讓它相依於租使用者提供者。
注意
相依性必須一律流向單一。 這表示 Scoped
服務可以相依於另一 Scoped
個 Singleton
服務或服務,但 Singleton
服務只能相依於其他 Singleton
服務: Transient => Scoped => Singleton
。
多個架構
警告
EF Core 並不直接支援此案例,而且不是建議的解決方案。
在不同的方法中,相同的資料庫可以使用資料表架構來處理 tenant1
和 tenant2
。
- Tenant1 -
tenant1.CustomerData
- Tenant2 -
tenant2.CustomerData
如果您未使用 EF Core 來處理具有移轉的資料庫更新,而且已經有多架構數據表,則可以覆寫 中的DbContext
OnModelCreating
架構,如下所示(數據表CustomerData
的架構設定為租使用者):
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<CustomerData>().ToTable(nameof(CustomerData), tenant);
多個資料庫和 連接字串
針對每個租用戶傳遞不同的 連接字串,會實作多個資料庫版本。 這可以在啟動時設定,方法是解析服務提供者,並使用它來建置 連接字串。 租使用者區段 連接字串 會新增至appsettings.json
組態檔。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"TenantA": "Data Source=tenantacontacts.sqlite",
"TenantB": "Data Source=tenantbcontacts.sqlite"
},
"AllowedHosts": "*"
}
服務和組態都會插入 :DbContext
public ContactContext(
DbContextOptions<ContactContext> opts,
IConfiguration config,
ITenantService service)
: base(opts)
{
_tenantService = service;
_configuration = config;
}
租用戶接著會用來查閱 中的 OnConfiguring
連接字串:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var tenant = _tenantService.Tenant;
var connectionStr = _configuration.GetConnectionString(tenant);
optionsBuilder.UseSqlite(connectionStr);
}
這適用於大部分案例,除非用戶可以在相同的會話期間切換租使用者。
切換租使用者
在先前針對多個資料庫的組態中,選項會在層級快 Scoped
取。 這表示如果使用者變更租使用者,則不會重新評估選項,因此租用戶變更不會反映在查詢中。
當租使用者可以變更時,此作業的簡單解決方案是將存留期設定為 Transient.
[這可確保每次要求租使用者時DbContext
,都會重新評估租使用者與 連接字串。 用戶可以盡可能常切換租使用者。 下表可協助您選擇最適合處理站的存留期。
案例 | 單一資料庫 | 多個資料庫 |
---|---|---|
使用者停留在單一租使用者中 | Scoped |
Scoped |
用戶可以切換租使用者 | Scoped |
Transient |
如果您的資料庫不採用用戶範圍相依性,預設值 Singleton
仍然有意義。
效能注意事項
EF Core 的設計目的是讓 DbContext
實例能以盡可能少的額外負荷快速具現化。 因此,建立每個作業的新 DbContext
作業通常應該沒問題。 如果此方法會影響應用程式的效能,請考慮使用 DbContext 共用。
結論
這是在 EF Core 應用程式中實作多租使用者的工作指引。 如果您有進一步的範例或案例,或想要提供意見反應,請 開啟問題 並參考這份檔。