Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Řada obchodních aplikací je navržená tak, aby fungovala s více zákazníky. Je důležité zabezpečit data tak, aby zákaznická data nebyla "unikla" ani nebyla viditelná jinými zákazníky a potenciálními konkurenty. Tyto aplikace jsou klasifikovány jako "víceklient", protože každý zákazník se považuje za tenanta aplikace s vlastní sadou dat.
Varování
Tento článek používá místní databázi, která nevyžaduje ověření uživatele. Produkční aplikace by měly používat nejbezpečnější dostupný tok ověřování. Další informace o ověřování v nasazených testovacích a produkčních aplikacích naleznete v Bezpečných autentizačních tocích.
Důležité
Tento dokument obsahuje příklady a řešení tak, jak jsou. Nejedná se o osvědčené postupy, ale spíše o pracovní postupy pro vaše úvahy.
Návod
Zdrojový kód pro tuto ukázku můžete zobrazit na GitHubu.
Podpora víceklientské architektury
Existuje mnoho přístupů k implementaci víceklientské architektury v aplikacích. Jedním z běžných přístupů (někdy je to požadavek) je uchovávat data pro každého zákazníka v samostatné databázi. Schéma je stejné, ale data jsou specifická pro zákazníky. Dalším přístupem je rozdělení dat do existující databáze zákazníkem. To lze provést pomocí sloupce v tabulce nebo s tabulkou ve více schématech se schématem pro každého tenanta.
| Přístup | Sloupec pro tenanta? | Schéma pro tenanta? | Více databází? | Podpora EF Core |
|---|---|---|---|---|
| Diskriminátor (sloupec) | Ano | Ne | Ne | Globální filtr dotazů |
| Databáze pro každého nájemce | Ne | Ne | Ano | Konfigurace |
| Schéma pro nájemce | Ne | Ano | Ne | Nepodporováno |
Pro přístup k databázi pro jednotlivé tenanty je přechod na správnou databázi stejně jednoduchý jako poskytnutí správných připojovací řetězec. Když jsou data uložená v jedné databázi, dá se globální filtr dotazů použít k automatickému filtrování řádků podle sloupce ID tenanta a zajistit, aby vývojáři nechtěně nenapsal kód, který má přístup k datům od jiných zákazníků.
Tyto příklady by měly fungovat správně ve většině modelů aplikací, včetně konzoly, WPF, WinForms a aplikací ASP.NET Core. Aplikace Blazor Serveru vyžadují zvláštní pozornost.
Aplikace Blazor Server a život továrny
Doporučeným vzorem pro použití Entity Framework Core v aplikacích Blazor je registrace DbContextFactory a jeho voláním vytvořit novou instanci DbContext každé operace. Ve výchozím nastavení je továrna jednoúčelová, takže pro všechny uživatele aplikace existuje jenom jedna kopie. To je obvykle v pořádku, protože i když je továrna sdílená, jednotlivé DbContext instance nejsou.
U víceklientské architektury se ale připojovací řetězec může změnit na uživatele. Vzhledem k tomu, že továrna ukládá konfiguraci do mezipaměti se stejnou životností, znamená to, že všichni uživatelé musí sdílet stejnou konfiguraci. Proto by se životnost měla změnit na Scoped.
K tomuto problému nedochází v aplikacích Blazor WebAssembly, protože singleton je vázán na uživatele. Aplikace Blazor Serveru na druhou stranu představují jedinečnou výzvu. I když je aplikace webovou aplikací, je "udržována naživu" komunikací v reálném čase pomocí služby SignalR. Relace se vytvoří pro každého uživatele a přetrvává i po počátečním požadavku. Aby bylo možné povolit nová nastavení, měla by být pro uživatele k dispozici nová továrna. Doba života pro tuto speciální továrnu je vymezena a pro každou uživatelskou relaci se vytvoří nová instance.
Ukázkové řešení (jednoúčelová databáze)
Možným řešením je vytvořit jednoduchou ITenantService službu, která zpracovává nastavení aktuálního tenanta uživatele. Poskytuje zpětná volání, aby kód byl upozorněn při změně tenanta. Implementace (s vynechanými zpětnými voláními pro přehlednost) může vypadat takto:
namespace Common
{
public interface ITenantService
{
string Tenant { get; }
void SetTenant(string tenant);
string[] GetTenants();
event TenantChangedEventHandler OnTenantChanged;
}
}
Ten DbContext pak může spravovat multitenantní prostředí. Přístup závisí na vaší databázové strategii. Pokud ukládáte všechny tenanty do jedné databáze, pravděpodobně použijete filtr dotazu.
ITenantService je předán konstruktoru prostřednictvím vkládání závislostí a používá se k rozpoznání a uložení identifikátoru tenanta.
public ContactContext(
DbContextOptions<ContactContext> opts,
ITenantService service)
: base(opts) => _tenant = service.Tenant;
Metoda OnModelCreating se přepíše a určí filtr dotazu:
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<MultitenantContact>()
.HasQueryFilter(mt => mt.Tenant == _tenant);
Tím zajistíte, že každý dotaz bude pro každou žádost vyfiltrován k tenantovi. V kódu aplikace není potřeba filtrovat, protože globální filtr se použije automaticky.
Poskytovatel tenanta a DbContextFactory jsou nakonfigurovány při spuštění aplikace takto, s použitím Sqlite jako příkladu:
builder.Services.AddDbContextFactory<ContactContext>(
opt => opt.UseSqlite("Data Source=singledb.sqlite"), ServiceLifetime.Scoped);
Všimněte si, že životnost služby je nakonfigurovaná pomocí ServiceLifetime.Scoped. To umožňuje převzít závislost na poskytovateli tenanta.
Poznámka:
Závislosti musí vždy směřovat k singletonu. To znamená, že Scoped služba může záviset na jiné Scoped službě nebo službě Singleton , ale Singleton služba může záviset pouze na jiných Singleton službách: Transient => Scoped => Singleton.
Více schémat
Varování
Tento scénář přímo nepodporuje EF Core a není doporučeným řešením.
Při jiném přístupu může stejná databáze zpracovávat tenant1 a tenant2 pomocí schémat tabulek.
-
Tenant1 -
tenant1.CustomerData -
Tenant2 -
tenant2.CustomerData
Pokud nepoužíváte EF Core ke zpracování aktualizací databáze pomocí migrací a už máte tabulky s více schématy, můžete přepsat schéma v rámci DbContext v OnModelCreating následujícím způsobem (schéma pro tabulku CustomerData je nastaveno na tenanta):
protected override void OnModelCreating(ModelBuilder modelBuilder) =>
modelBuilder.Entity<CustomerData>().ToTable(nameof(CustomerData), tenant);
Více databází a připojovací řetězec
Více verzí databází se implementuje předáním jiného připojovacího řetězce pro každého nájemce. Je možné to nakonfigurovat při spuštění vyřešením poskytovatele služeb a jeho použitím k sestavení řetězce připojení. Do konfiguračního souboru se přidá připojovací řetězec pro oddíl tenanta appsettings.json.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"TenantA": "Data Source=tenantacontacts.sqlite",
"TenantB": "Data Source=tenantbcontacts.sqlite"
},
"AllowedHosts": "*"
}
Služba i konfigurace se vloží do DbContext:
public ContactContext(
DbContextOptions<ContactContext> opts,
IConfiguration config,
ITenantService service)
: base(opts)
{
_tenantService = service;
_configuration = config;
}
Tenant se pak použije k vyhledání připojovacího řetězce v OnConfiguring.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var tenant = _tenantService.Tenant;
var connectionStr = _configuration.GetConnectionString(tenant);
optionsBuilder.UseSqlite(connectionStr);
}
Funguje to správně pro většinu scénářů, pokud uživatel nemůže přepnout tenanty během stejné relace.
Přepínání tenantů
V předchozí konfiguraci pro více databází se možnosti ukládají do mezipaměti na Scoped úrovni. To znamená, že pokud uživatel změní tenanta, možnosti se znovu nevyhodnocují , takže se změna tenanta neprojeví v dotazech.
Snadné řešení pro to, když se tenant může změnit, je nastavení doby životnosti na Transient., což zajišťuje, že se tenant znovu vyhodnotí spolu s připojovacím řetězcem DbContext pokaždé, když je požadován. Uživatel může tenanty přepínat tak často, jak se jim líbí. Následující tabulka vám pomůže zvolit, která doba života je pro vaši továrnu nejvhodnější.
| Scénář | Jednoúčelová databáze | Více databází |
|---|---|---|
| Uživatel zůstává u jednoho nájemce. | Scoped |
Scoped |
| Uživatel může přepínat tenanty | Scoped |
Transient |
Výchozí hodnota Singleton stále dává smysl, pokud vaše databáze nepřebírá uživatelsky orientované závislosti.
Poznámky k výkonu
EF Core byl navržen tak, aby bylo možné DbContext rychle inicializovat instance s co nejmenší zátěží. Z tohoto důvodu by mělo být vytvoření nové DbContext operace obvykle v pořádku. Pokud tento přístup ovlivňuje výkon vaší aplikace, zvažte použití sdružování DbContextu.
Závěr
Toto jsou praktické pokyny pro implementaci multitenance v aplikacích EF Core. Pokud máte další příklady nebo scénáře nebo chcete poskytnout zpětnou vazbu, otevřete problém a projděte si tento dokument.