共用方式為


多組織用戶管理

許多企業營運應用程式的設計目的是要與多個客戶合作。 請務必保護數據,讓客戶數據不會「外泄」或由其他客戶和潛在競爭對手看到。 這些應用程式會分類為「多租使用者」,因為每個客戶都會被視為具有自己數據集的應用程式租使用者。

警告

本文使用本機資料庫,其不需要使用者進行驗證。 實際執行應用程式應該使用可用的最安全驗證流程。 如需已部署測試與實際執行應用程式驗證的詳細資訊,請參閱安全驗證流程

重要

本檔提供「如目前」的範例和解決方案。這些不是「最佳做法」,而是要考慮「工作作法」。

提示

您可以在 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 服務可以相依於另一 ScopedSingleton 服務或服務,但 Singleton 服務只能相依於其他 Singleton 服務: Transient => Scoped => Singleton

多個架構

警告

EF Core 並不直接支援此案例,而且不是建議的解決方案。

在不同的方法中,相同的資料庫可以使用資料表架構來處理 tenant1tenant2

  • Tenant1 - tenant1.CustomerData
  • Tenant2 - tenant2.CustomerData

如果您未使用 EF Core 來處理具有移轉的資料庫更新,而且已經有多架構數據表,則可以覆寫 中的DbContextOnModelCreating架構,如下所示(數據表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 應用程式中實作多租使用者的工作指引。 如果您有進一步的範例或案例,或想要提供意見反應,請 開啟問題 並參考這份檔。