Wielodostęp

Wiele aplikacji biznesowych jest przeznaczonych do pracy z wieloma klientami. Ważne jest, aby zabezpieczyć dane, aby dane klientów nie zostały "ujawnione" ani widoczne dla innych klientów i potencjalnych konkurentów. Te aplikacje są klasyfikowane jako "wielodostępne", ponieważ każdy klient jest uważany za dzierżawę aplikacji z własnym zestawem danych.

Ważne

Ten dokument zawiera przykłady i rozwiązania "jak to jest". Nie mają one być "najlepszymi rozwiązaniami", ale raczej "praktykami roboczymi" do rozważenia.

Obsługa wielu dzierżaw

Istnieje wiele podejść do implementowania wielu dzierżaw w aplikacjach. Jednym z typowych podejść (czasami jest to wymaganie) jest przechowywanie danych dla każdego klienta w oddzielnej bazie danych. Schemat jest taki sam, ale dane są specyficzne dla klienta. Innym podejściem jest partycjonowanie danych w istniejącej bazie danych przez klienta. Można to zrobić przy użyciu kolumny w tabeli lub tabeli w wielu schematach ze schematem dla każdej dzierżawy.

Metoda Kolumna dla dzierżawy? Schemat na dzierżawę? Wiele baz danych? Obsługa platformy EF Core
Dyskryminujące (kolumna) Tak Nie. Nie. Globalny filtr zapytań
Baza danych dla każdego dzierżawcy Nie. Nie Tak Konfigurowanie
Schemat na dzierżawę Nie. Tak Nie. Nieobsługiwane

W przypadku podejścia bazy danych na dzierżawę przełączenie do właściwej bazy danych jest tak proste, jak zapewnienie poprawnego parametry połączenia. Gdy dane są przechowywane w pojedynczej bazie danych, globalny filtr zapytań może służyć do automatycznego filtrowania wierszy według kolumny identyfikatora dzierżawy, dzięki czemu deweloperzy nie będą przypadkowo pisać kodu, który może uzyskiwać dostęp do danych od innych klientów.

Te przykłady powinny działać dobrze w większości modeli aplikacji, w tym w konsoli, WPF, WinForms i ASP.NET Core. Aplikacje blazor Server wymagają szczególnej uwagi.

Aplikacje blazor Server i żywotność fabryki

Zalecanym wzorcem korzystania z platformy Entity Framework Core w aplikacjach Platformy Blazor jest zarejestrowanie elementu DbContextFactory, a następnie wywołanie go w celu utworzenia nowego wystąpienia DbContext każdej operacji. Domyślnie fabryka jest pojedynczą kopią, więc dla wszystkich użytkowników aplikacji istnieje tylko jedna kopia. Zwykle jest to w porządku, ponieważ mimo że fabryka jest współdzielona, poszczególne DbContext wystąpienia nie są.

Jednak w przypadku wielu dzierżaw parametry połączenia może ulec zmianie na użytkownika. Ponieważ fabryka buforuje konfigurację z tym samym okresem istnienia, oznacza to, że wszyscy użytkownicy muszą współużytkować tę samą konfigurację. W związku z tym okres istnienia należy zmienić na Scoped.

Ten problem nie występuje w aplikacjach zestawu WebAssembly platformy Blazor, ponieważ pojedynczy element jest zakresem użytkownika. Z drugiej strony aplikacje blazor Server stanowią wyjątkowe wyzwanie. Chociaż aplikacja jest aplikacją internetową, jest "utrzymywana przy życiu" przez komunikację w czasie rzeczywistym przy użyciu usługi SignalR. Sesja jest tworzona dla użytkownika i trwa poza początkowym żądaniem. Nowa fabryka powinna być udostępniana dla każdego użytkownika, aby zezwolić na nowe ustawienia. Okres istnienia tej specjalnej fabryki jest o określonym zakresie i tworzone jest nowe wystąpienie na sesję użytkownika.

Przykładowe rozwiązanie (pojedyncza baza danych)

Możliwe rozwiązanie polega na utworzeniu prostej ITenantService usługi obsługującej ustawianie bieżącej dzierżawy użytkownika. Zapewnia wywołania zwrotne, dzięki czemu kod jest powiadamiany o zmianie dzierżawy. Implementacja (z pominięciem wywołań zwrotnych dla jasności) może wyglądać następująco:

namespace Common
{
    public interface ITenantService
    {
        string Tenant { get; }

        void SetTenant(string tenant);

        string[] GetTenants();

        event TenantChangedEventHandler OnTenantChanged;
    }
}

Następnie DbContext może zarządzać wielodostępnością. Podejście zależy od strategii bazy danych. Jeśli przechowujesz wszystkie dzierżawy w jednej bazie danych, prawdopodobnie użyjesz filtru zapytania. Element ITenantService jest przekazywany do konstruktora za pośrednictwem wstrzykiwania zależności i służy do rozpoznawania i przechowywania identyfikatora dzierżawy.

public ContactContext(
    DbContextOptions<ContactContext> opts,
    ITenantService service)
    : base(opts) => _tenant = service.Tenant;

Metoda OnModelCreating jest zastępowana w celu określenia filtru zapytania:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    => modelBuilder.Entity<MultitenantContact>()
        .HasQueryFilter(mt => mt.Tenant == _tenant);

Dzięki temu każde zapytanie jest filtrowane do dzierżawy w każdym żądaniu. Nie ma potrzeby filtrowania w kodzie aplikacji, ponieważ filtr globalny zostanie zastosowany automatycznie.

Dostawca dzierżawy i DbContextFactory są skonfigurowane podczas uruchamiania aplikacji w następujący sposób, korzystając z witryny Sqlite jako przykładu:

builder.Services.AddDbContextFactory<ContactContext>(
    opt => opt.UseSqlite("Data Source=singledb.sqlite"), ServiceLifetime.Scoped);

Zwróć uwagę, że okres istnienia usługi jest skonfigurowany przy użyciu polecenia ServiceLifetime.Scoped. Umożliwia to podjęcie zależności od dostawcy dzierżawy.

Uwaga

Zależności muszą zawsze przepływać w kierunku pojedynczego. Oznacza to, że Scoped usługa może zależeć od innej Scoped usługi lub Singleton usługi, ale Singleton usługa może zależeć tylko od innych Singleton usług: Transient => Scoped => Singleton.

Wiele schematów

Ostrzeżenie

Ten scenariusz nie jest bezpośrednio obsługiwany przez program EF Core i nie jest zalecanym rozwiązaniem.

W innym podejściu ta sama baza danych może obsługiwać tenant1 schematy tabel i tenant2 przy użyciu nich.

  • Dzierżawa1 - tenant1.CustomerData
  • Dzierżawa2 - tenant2.CustomerData

Jeśli nie używasz programu EF Core do obsługi aktualizacji bazy danych z migracjami i masz już tabele z wieloma schematami, możesz przesłonić schemat w pliku DbContext w OnModelCreating następujący sposób (schemat tabeli CustomerData jest ustawiony na dzierżawę):

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
    modelBuilder.Entity<CustomerData>().ToTable(nameof(CustomerData), tenant);

Wiele baz danych i parametry połączenia

Wiele wersji bazy danych jest implementowanych przez przekazanie różnych parametry połączenia dla każdej dzierżawy. Można to skonfigurować podczas uruchamiania, rozpoznając dostawcę usług i używając go do skompilowania parametry połączenia. Sekcja parametry połączenia według dzierżawy jest dodawana do appsettings.json pliku konfiguracji.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "TenantA": "Data Source=tenantacontacts.sqlite",
    "TenantB": "Data Source=tenantbcontacts.sqlite"
  },
  "AllowedHosts": "*"
}

Usługa i konfiguracja są wstrzykiwane do elementu DbContext:

public ContactContext(
    DbContextOptions<ContactContext> opts,
    IConfiguration config,
    ITenantService service)
    : base(opts)
{
    _tenantService = service;
    _configuration = config;
}

Dzierżawa jest następnie używana do wyszukiwania parametry połączenia w pliku OnConfiguring:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var tenant = _tenantService.Tenant;
    var connectionStr = _configuration.GetConnectionString(tenant);
    optionsBuilder.UseSqlite(connectionStr);
}

Działa to dobrze w przypadku większości scenariuszy, chyba że użytkownik może przełączać dzierżawy podczas tej samej sesji.

Przełączanie dzierżaw

W poprzedniej konfiguracji dla wielu baz danych opcje są buforowane na Scoped poziomie. Oznacza to, że jeśli użytkownik zmieni dzierżawę, opcje nie zostaną ponownie zwalczone, a więc zmiana dzierżawy nie zostanie odzwierciedlona w zapytaniach.

Rozwiązaniem tego problemu, gdy dzierżawa może ulec zmianie, jest ustawienie okresu istnienia Transient. na wartość To gwarantuje, że dzierżawa jest ponownie oceniana wraz z parametry połączenia za każdym razem, DbContext gdy jest wymagane. Użytkownik może przełączać dzierżawy tak często, jak lubią. W poniższej tabeli przedstawiono wybór okresu istnienia, który jest najbardziej odpowiedni dla twojej fabryki.

Scenariusz Pojedyncza baza danych Wiele baz danych
Użytkownik pozostaje w jednej dzierżawie Scoped Scoped
Użytkownik może przełączać dzierżawy Scoped Transient

Wartość domyślna Singleton nadal ma sens, jeśli baza danych nie korzysta z zależności o zakresie użytkownika.

Uwagi dotyczące wydajności

Program EF Core został zaprojektowany tak, aby DbContext wystąpienia mogły być tworzone szybko z jak najmniejszym obciążeniem. Z tego powodu utworzenie nowej DbContext operacji powinno być zwykle w porządku. Jeśli takie podejście ma wpływ na wydajność aplikacji, rozważ użycie buforowania DbContext.

Podsumowanie

To działa wskazówki dotyczące implementowania wielu dzierżaw w aplikacjach platformy EF Core. Jeśli masz więcej przykładów lub scenariuszy lub chcesz przekazać opinię, otwórz problem i odwołaj się do tego dokumentu.