Udostępnij za pośrednictwem


Identity dostosowywanie modelu w programie ASP.NET Core

Autor: Arthur Vickers

ASP.NET Core Identity udostępnia platformę do zarządzania kontami użytkowników i ich przechowywania w aplikacjach platformy ASP.NET Core. Identity element jest dodawany do projektu, gdy jako mechanizm uwierzytelniania wybrano opcję Indywidualne konta użytkowników. Domyślnie Identity korzysta z modelu danych platformy Entity Framework (EF) Core. W tym artykule opisano sposób dostosowywania Identity modelu.

Identity i EF Core migracje

Przed zbadaniem modelu warto zrozumieć, jak Identity działa z migracjami EF Core w celu utworzenia i zaktualizowania bazy danych. Na najwyższym poziomie proces to:

  1. Definiowanie lub aktualizowanie modelu danych w kodzie.
  2. Dodaj migrację, aby przetłumaczyć ten model na zmiany, które można zastosować do bazy danych.
  3. Sprawdź, czy migracja poprawnie reprezentuje twoje intencje.
  4. Zastosuj migrację, aby zaktualizować bazę danych do synchronizacji z modelem.
  5. Powtórz kroki od 1 do 4, aby jeszcze bardziej uściślić model i zachować synchronizację bazy danych.

Użyj jednej z następujących metod dodawania i stosowania migracji:

  • Okno Menedżer pakietów Console (PMC) w przypadku korzystania z programu Visual Studio. Aby uzyskać więcej informacji, zobacz EF Core narzędzia PMC.
  • Interfejs wiersza polecenia platformy .NET, jeśli używasz wiersza polecenia. Aby uzyskać więcej informacji, zobacz EF Core narzędzia wiersza polecenia platformy .NET.
  • Kliknięcie przycisku Zastosuj migracje na stronie błędu po uruchomieniu aplikacji.

ASP.NET Core ma program obsługi strony błędu czasu programowania. Program obsługi może stosować migracje po uruchomieniu aplikacji. Aplikacje produkcyjne zwykle generują skrypty SQL na podstawie migracji i wdrażają zmiany bazy danych w ramach kontrolowanego wdrożenia aplikacji i bazy danych.

Po utworzeniu nowej aplikacji przy użyciu Identity kroków 1 i 2 powyżej zostały już ukończone. Oznacza to, że początkowy model danych już istnieje i migracja początkowa została dodana do projektu. Migracja początkowa nadal musi zostać zastosowana do bazy danych. Migrację początkową można zastosować za pomocą jednej z następujących metod:

  • Uruchom polecenie Update-Database w usłudze PMC.
  • Uruchom polecenie dotnet ef database update w powłoce poleceń.
  • Kliknij przycisk Zastosuj migracje na stronie błędu po uruchomieniu aplikacji.

Powtórz powyższe kroki w miarę wprowadzania zmian w modelu.

Identity Model

Typy jednostek

Model Identity składa się z następujących typów jednostek.

Typ encji opis
User Reprezentuje użytkownika.
Role Reprezentuje rolę.
UserClaim Reprezentuje oświadczenie, które posiada użytkownik.
UserToken Reprezentuje token uwierzytelniania dla użytkownika.
UserLogin Kojarzy użytkownika z identyfikatorem logowania.
RoleClaim Reprezentuje oświadczenie przyznane wszystkim użytkownikom w ramach roli.
UserRole Jednostka sprzężenia, która kojarzy użytkowników i role.

Relacje typu jednostki

Typy jednostek są ze sobą powiązane w następujący sposób:

  • Każdy User może mieć wiele UserClaimselementów .
  • Każdy User może mieć wiele UserLoginselementów .
  • Każdy User może mieć wiele UserTokenselementów .
  • Każdy Role może mieć wiele skojarzonych elementów RoleClaims.
  • Każda może mieć wiele skojarzonych elementów Roles, a każda User z nich Role może być skojarzona z wieloma Users. Jest to relacja wiele-do-wielu, która wymaga tabeli sprzężenia w bazie danych. Tabela sprzężenia jest reprezentowana UserRole przez jednostkę.

Domyślna konfiguracja modelu

Identity definiuje wiele klas kontekstowych , które dziedziczą z DbContext , aby skonfigurować i używać modelu. Ta konfiguracja jest wykonywana przy użyciu interfejsu EF Core API Code First Fluent w OnModelCreating metodzie klasy kontekstu. Domyślna konfiguracja to:

builder.Entity<TUser>(b =>
{
    // Primary key
    b.HasKey(u => u.Id);

    // Indexes for "normalized" username and email, to allow efficient lookups
    b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
    b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");

    // Maps to the AspNetUsers table
    b.ToTable("AspNetUsers");

    // A concurrency token for use with the optimistic concurrency checking
    b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();

    // Limit the size of columns to use efficient database types
    b.Property(u => u.UserName).HasMaxLength(256);
    b.Property(u => u.NormalizedUserName).HasMaxLength(256);
    b.Property(u => u.Email).HasMaxLength(256);
    b.Property(u => u.NormalizedEmail).HasMaxLength(256);

    // The relationships between User and other entity types
    // Note that these relationships are configured with no navigation properties

    // Each User can have many UserClaims
    b.HasMany<TUserClaim>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();

    // Each User can have many UserLogins
    b.HasMany<TUserLogin>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();

    // Each User can have many UserTokens
    b.HasMany<TUserToken>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();

    // Each User can have many entries in the UserRole join table
    b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});

builder.Entity<TUserClaim>(b =>
{
    // Primary key
    b.HasKey(uc => uc.Id);

    // Maps to the AspNetUserClaims table
    b.ToTable("AspNetUserClaims");
});

builder.Entity<TUserLogin>(b =>
{
    // Composite primary key consisting of the LoginProvider and the key to use
    // with that provider
    b.HasKey(l => new { l.LoginProvider, l.ProviderKey });

    // Limit the size of the composite key columns due to common DB restrictions
    b.Property(l => l.LoginProvider).HasMaxLength(128);
    b.Property(l => l.ProviderKey).HasMaxLength(128);

    // Maps to the AspNetUserLogins table
    b.ToTable("AspNetUserLogins");
});

builder.Entity<TUserToken>(b =>
{
    // Composite primary key consisting of the UserId, LoginProvider and Name
    b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name });

    // Limit the size of the composite key columns due to common DB restrictions
    b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength);
    b.Property(t => t.Name).HasMaxLength(maxKeyLength);

    // Maps to the AspNetUserTokens table
    b.ToTable("AspNetUserTokens");
});

builder.Entity<TRole>(b =>
{
    // Primary key
    b.HasKey(r => r.Id);

    // Index for "normalized" role name to allow efficient lookups
    b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex").IsUnique();

    // Maps to the AspNetRoles table
    b.ToTable("AspNetRoles");

    // A concurrency token for use with the optimistic concurrency checking
    b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();

    // Limit the size of columns to use efficient database types
    b.Property(u => u.Name).HasMaxLength(256);
    b.Property(u => u.NormalizedName).HasMaxLength(256);

    // The relationships between Role and other entity types
    // Note that these relationships are configured with no navigation properties

    // Each Role can have many entries in the UserRole join table
    b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();

    // Each Role can have many associated RoleClaims
    b.HasMany<TRoleClaim>().WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
});

builder.Entity<TRoleClaim>(b =>
{
    // Primary key
    b.HasKey(rc => rc.Id);

    // Maps to the AspNetRoleClaims table
    b.ToTable("AspNetRoleClaims");
});

builder.Entity<TUserRole>(b =>
{
    // Primary key
    b.HasKey(r => new { r.UserId, r.RoleId });

    // Maps to the AspNetUserRoles table
    b.ToTable("AspNetUserRoles");
});

Typy ogólne modelu

Identity definiuje domyślne typy środowiska uruchomieniowego języka wspólnego (CLR) dla każdego z typów jednostek wymienionych powyżej. Wszystkie te typy są poprzedzone prefiksem Identity:

  • IdentityUser
  • IdentityRole
  • IdentityUserClaim
  • IdentityUserToken
  • IdentityUserLogin
  • IdentityRoleClaim
  • IdentityUserRole

Zamiast bezpośrednio używać tych typów, typy mogą być używane jako klasy bazowe dla własnych typów aplikacji. Klasy DbContext zdefiniowane przez Identity są ogólne, tak aby różne typy CLR mogły być używane dla co najmniej jednego typu jednostki w modelu. Te typy ogólne umożliwiają również zmianę typu danych klucza podstawowego User (PK).

W przypadku korzystania Identity z obsługi ról IdentityDbContext należy użyć klasy . Na przykład:

// Uses all the built-in Identity types
// Uses `string` as the key type
public class IdentityDbContext
    : IdentityDbContext<IdentityUser, IdentityRole, string>
{
}

// Uses the built-in Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityDbContext<TUser>
    : IdentityDbContext<TUser, IdentityRole, string>
        where TUser : IdentityUser
{
}

// Uses the built-in Identity types except with custom User and Role types
// The key type is defined by TKey
public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<
    TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>,
    IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
        where TUser : IdentityUser<TKey>
        where TRole : IdentityRole<TKey>
        where TKey : IEquatable<TKey>
{
}

// No built-in Identity types are used; all are specified by generic arguments
// The key type is defined by TKey
public abstract class IdentityDbContext<
    TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken>
    : IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
         where TUser : IdentityUser<TKey>
         where TRole : IdentityRole<TKey>
         where TKey : IEquatable<TKey>
         where TUserClaim : IdentityUserClaim<TKey>
         where TUserRole : IdentityUserRole<TKey>
         where TUserLogin : IdentityUserLogin<TKey>
         where TRoleClaim : IdentityRoleClaim<TKey>
         where TUserToken : IdentityUserToken<TKey>

Można również używać Identity bez ról (tylko oświadczeń), w tym przypadku IdentityUserContext<TUser> należy użyć klasy:

// Uses the built-in non-role Identity types except with a custom User type
// Uses `string` as the key type
public class IdentityUserContext<TUser>
    : IdentityUserContext<TUser, string>
        where TUser : IdentityUser
{
}

// Uses the built-in non-role Identity types except with a custom User type
// The key type is defined by TKey
public class IdentityUserContext<TUser, TKey> : IdentityUserContext<
    TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>,
    IdentityUserToken<TKey>>
        where TUser : IdentityUser<TKey>
        where TKey : IEquatable<TKey>
{
}

// No built-in Identity types are used; all are specified by generic arguments, with no roles
// The key type is defined by TKey
public abstract class IdentityUserContext<
    TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
        where TUser : IdentityUser<TKey>
        where TKey : IEquatable<TKey>
        where TUserClaim : IdentityUserClaim<TKey>
        where TUserLogin : IdentityUserLogin<TKey>
        where TUserToken : IdentityUserToken<TKey>
{
}

Dostosowywanie modelu

Punktem początkowym dostosowywania modelu jest wyprowadzenie z odpowiedniego typu kontekstu. Zobacz sekcję Typy ogólne modelu. Ten typ kontekstu jest wywoływany niestandardowie ApplicationDbContext i jest tworzony przez szablony ASP.NET Core.

Kontekst służy do konfigurowania modelu na dwa sposoby:

  • Dostarczanie typów jednostek i kluczy dla parametrów typu ogólnego.
  • Zastępowanie OnModelCreating w celu zmodyfikowania mapowania tych typów.

Podczas zastępowania OnModelCreatingbase.OnModelCreating należy najpierw wywołać metodę , a następnie wywołać należy zastąpić konfigurację. EF Core zazwyczaj ma zasady last-one-wins dla konfiguracji. Jeśli na przykład ToTable metoda typu jednostki jest wywoływana jako pierwsza z jedną nazwą tabeli, a następnie ponownie z inną nazwą tabeli, używana jest nazwa tabeli w drugim wywołaniu.

UWAGA: Jeśli element DbContext nie pochodzi z IdentityDbContextklasy , AddEntityFrameworkStores może nie wywnioskować poprawnych typów POCO dla TUserClaim, TUserLogini TUserToken. Jeśli AddEntityFrameworkStores nie wywnioskuje prawidłowych typów POCO, obejście polega na bezpośrednim dodaniu poprawnych typów za pomocą metod services.AddScoped<IUser/RoleStore<TUser> i UserStore<...>>.

Niestandardowe dane użytkownika

Niestandardowe dane użytkownika są obsługiwane przez dziedziczenie z IdentityUserelementu . Nazwa tego typu ApplicationUserjest niestandardowa:

public class ApplicationUser : IdentityUser
{
    public string CustomTag { get; set; }
}

Użyj typu jako argumentu ApplicationUser ogólnego kontekstu:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

W klasie nie ma potrzeby zastępowania OnModelCreatingApplicationDbContext . EF Core mapuje właściwość zgodnie z konwencją CustomTag . Należy jednak zaktualizować bazę danych, aby utworzyć nową CustomTag kolumnę. Aby utworzyć kolumnę, dodaj migrację, a następnie zaktualizuj bazę danych zgodnie z opisem w temacie Identity i EF Core Migracje.

Zaktualizuj Pages/Shared/_LoginPartial.cshtml i zastąp element IdentityUser ciągiem ApplicationUser:

@using Microsoft.AspNetCore.Identity
@using WebApp1.Areas.Identity.Data
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager

Zaktualizuj Areas/Identity/IdentityHostingStartup.cs lub Startup.ConfigureServices zastąp element IdentityUser ciągiem ApplicationUser.

services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();                                    

Wywołanie AddDefaultIdentity jest równoważne następującemu kodowi:

services.AddAuthentication(o =>
{
    o.DefaultScheme = IdentityConstants.ApplicationScheme;
    o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });

services.AddIdentityCore<TUser>(o =>
{
    o.Stores.MaxLengthForKeys = 128;
    o.SignIn.RequireConfirmedAccount = true;
})
.AddDefaultUI()
.AddDefaultTokenProviders();

Identity jest dostarczany jako Razor biblioteka klas. Aby uzyskać więcej informacji, zobacz Szkielet Identity w projektach ASP.NET Core. W związku z tym powyższy kod wymaga wywołania metody AddDefaultUI. Identity Jeśli szkielet został użyty do dodawania Identity plików do projektu, usuń wywołanie metody AddDefaultUI. Aby uzyskać więcej informacji, zobacz:

Zmienianie typu klucza podstawowego

Zmiana typu danych kolumny PK po utworzeniu bazy danych jest problematyczna w wielu systemach baz danych. Zmiana klucza PK zwykle polega na upuszczaniu i ponownym tworzeniu tabeli. W związku z tym typy kluczy powinny być określone w początkowej migracji podczas tworzenia bazy danych.

Wykonaj następujące kroki, aby zmienić typ PK:

  1. Jeśli baza danych została utworzona przed zmianą PK, uruchom polecenie Drop-Database (PMC) lub dotnet ef database drop (interfejs wiersza polecenia platformy .NET), aby go usunąć.

  2. Po potwierdzeniu usunięcia bazy danych usuń migrację początkową za pomocą Remove-Migration (PMC) lub dotnet ef migrations remove (interfejs wiersza polecenia platformy .NET).

  3. Zaktualizuj klasę ApplicationDbContext , aby pochodziła z IdentityDbContext<TUser,TRole,TKey>klasy . Określ nowy typ klucza dla elementu TKey. Aby na przykład użyć Guid typu klucza:

    public class ApplicationDbContext
        : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    W poprzednim kodzie klasy IdentityUser<TKey> ogólne i IdentityRole<TKey> muszą być określone, aby używać nowego typu klucza.

    Startup.ConfigureServices należy zaktualizować w celu używania użytkownika ogólnego:

    services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    
  4. Jeśli jest używana klasa niestandardowa ApplicationUser , zaktualizuj klasę, aby dziedziczyła z IdentityUserklasy . Na przykład:

    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationUser : IdentityUser<Guid>
    {
        public string CustomTag { get; set; }
    }
    

    Zaktualizuj ApplicationDbContext , aby odwołać się do klasy niestandardowej ApplicationUser :

    public class ApplicationDbContext
        : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    Zarejestruj niestandardową klasę kontekstu bazy danych podczas dodawania Identity usługi w programie Startup.ConfigureServices:

    services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    

    Typ danych klucza podstawowego DbContext jest wnioskowany przez analizę obiektu.

    Identity jest dostarczany jako Razor biblioteka klas. Aby uzyskać więcej informacji, zobacz Szkielet Identity w projektach ASP.NET Core. W związku z tym powyższy kod wymaga wywołania metody AddDefaultUI. Identity Jeśli szkielet został użyty do dodawania Identity plików do projektu, usuń wywołanie metody AddDefaultUI.

  5. Jeśli jest używana klasa niestandardowa ApplicationRole , zaktualizuj klasę, aby dziedziczyła z IdentityRole<TKey>klasy . Na przykład:

    using System;
    using Microsoft.AspNetCore.Identity;
    
    public class ApplicationRole : IdentityRole<Guid>
    {
        public string Description { get; set; }
    }
    

    Zaktualizuj ApplicationDbContext , aby odwołać się do klasy niestandardowej ApplicationRole . Na przykład następująca klasa odwołuje się do niestandardowego ApplicationUser i niestandardowego ApplicationRoleelementu :

    using System;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    
    public class ApplicationDbContext :
        IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
    

    Zarejestruj niestandardową klasę kontekstu bazy danych podczas dodawania Identity usługi w programie Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });
    
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
    
        services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultUI()
                .AddDefaultTokenProviders();
    
        services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
    

    Typ danych klucza podstawowego DbContext jest wnioskowany przez analizę obiektu.

    Identity jest dostarczany jako Razor biblioteka klas. Aby uzyskać więcej informacji, zobacz Szkielet Identity w projektach ASP.NET Core. W związku z tym powyższy kod wymaga wywołania metody AddDefaultUI. Identity Jeśli szkielet został użyty do dodawania Identity plików do projektu, usuń wywołanie metody AddDefaultUI.

Dodawanie właściwości nawigacji

Zmiana konfiguracji modelu dla relacji może być trudniejsza niż wprowadzanie innych zmian. Należy zachować ostrożność, aby zastąpić istniejące relacje, zamiast tworzyć nowe, dodatkowe relacje. W szczególności zmieniona relacja musi określać tę samą właściwość klucza obcego (FK) co istniejąca relacja. Na przykład relacja między elementami Users i UserClaims jest domyślnie określona w następujący sposób:

builder.Entity<TUser>(b =>
{
    // Each User can have many UserClaims
    b.HasMany<TUserClaim>()
     .WithOne()
     .HasForeignKey(uc => uc.UserId)
     .IsRequired();
});

Klucz FK dla tej relacji jest określony jako UserClaim.UserId właściwość. HasMany i WithOne są wywoływane bez argumentów, aby utworzyć relację bez właściwości nawigacji.

Dodaj właściwość nawigacji, do ApplicationUser której można odwoływać się do skojarzenia UserClaims z użytkownikiem:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
}

Parametr for TKeyIdentityUserClaim<TKey> jest typem określonym dla klucza PK użytkowników. W tym przypadku jest to string spowodowane tym, TKey że są używane wartości domyślne. Nie jest to typ PK dla UserClaim typu jednostki.

Teraz, gdy właściwość nawigacji istnieje, należy ją skonfigurować w pliku OnModelCreating:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();
        });
    }
}

Zwróć uwagę, że relacja jest skonfigurowana dokładnie tak, jak wcześniej, tylko z właściwością nawigacji określoną w wywołaniu metody HasMany.

Właściwości nawigacji istnieją tylko w modelu EF, a nie w bazie danych. Ponieważ klucz FK dla relacji nie uległ zmianie, tego rodzaju zmiana modelu nie wymaga zaktualizowania bazy danych. Można to sprawdzić, dodając migrację po wprowadzeniu zmiany. Metody Up i Down są puste.

Dodaj wszystkie właściwości nawigacji użytkownika

Korzystając z powyższej sekcji jako wskazówek, poniższy przykład konfiguruje jednokierunkowe właściwości nawigacji dla wszystkich relacji w użytkowniku:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
    public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne()
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne()
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne()
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });
    }
}

Dodawanie właściwości nawigacji użytkownika i roli

Korzystając z powyższej sekcji jako wskazówek, poniższy przykład konfiguruje właściwości nawigacji dla wszystkich relacji w użytkowniku i roli:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
    public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
    public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

public class ApplicationRole : IdentityRole
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}
public class ApplicationDbContext
    : IdentityDbContext<
        ApplicationUser, ApplicationRole, string,
        IdentityUserClaim<string>, ApplicationUserRole, IdentityUserLogin<string>,
        IdentityRoleClaim<string>, IdentityUserToken<string>>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne()
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne()
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne()
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.User)
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });

        modelBuilder.Entity<ApplicationRole>(b =>
        {
            // Each Role can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.Role)
                .HasForeignKey(ur => ur.RoleId)
                .IsRequired();
        });

    }
}

Uwagi:

  • Ten przykład obejmuje UserRole również jednostkę sprzężenia, która jest wymagana do nawigowania relacji wiele do wielu z obszaru Użytkownicy do ról.
  • Pamiętaj, aby zmienić typy właściwości nawigacji, aby odzwierciedlić, że Application{...} typy są teraz używane zamiast Identity{...} typów.
  • Pamiętaj, aby użyć elementu Application{...} w definicji ogólnej ApplicationContext .

Dodaj wszystkie właściwości nawigacji

Korzystając z powyższej sekcji jako wskazówek, poniższy przykład konfiguruje właściwości nawigacji dla wszystkich relacji we wszystkich typach jednostek:

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<ApplicationUserClaim> Claims { get; set; }
    public virtual ICollection<ApplicationUserLogin> Logins { get; set; }
    public virtual ICollection<ApplicationUserToken> Tokens { get; set; }
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}

public class ApplicationRole : IdentityRole
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
    public virtual ICollection<ApplicationRoleClaim> RoleClaims { get; set; }
}

public class ApplicationUserRole : IdentityUserRole<string>
{
    public virtual ApplicationUser User { get; set; }
    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationUserClaim : IdentityUserClaim<string>
{
    public virtual ApplicationUser User { get; set; }
}

public class ApplicationUserLogin : IdentityUserLogin<string>
{
    public virtual ApplicationUser User { get; set; }
}

public class ApplicationRoleClaim : IdentityRoleClaim<string>
{
    public virtual ApplicationRole Role { get; set; }
}

public class ApplicationUserToken : IdentityUserToken<string>
{
    public virtual ApplicationUser User { get; set; }
}
public class ApplicationDbContext
    : IdentityDbContext<
        ApplicationUser, ApplicationRole, string,
        ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin,
        ApplicationRoleClaim, ApplicationUserToken>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationUser>(b =>
        {
            // Each User can have many UserClaims
            b.HasMany(e => e.Claims)
                .WithOne(e => e.User)
                .HasForeignKey(uc => uc.UserId)
                .IsRequired();

            // Each User can have many UserLogins
            b.HasMany(e => e.Logins)
                .WithOne(e => e.User)
                .HasForeignKey(ul => ul.UserId)
                .IsRequired();

            // Each User can have many UserTokens
            b.HasMany(e => e.Tokens)
                .WithOne(e => e.User)
                .HasForeignKey(ut => ut.UserId)
                .IsRequired();

            // Each User can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.User)
                .HasForeignKey(ur => ur.UserId)
                .IsRequired();
        });

        modelBuilder.Entity<ApplicationRole>(b =>
        {
            // Each Role can have many entries in the UserRole join table
            b.HasMany(e => e.UserRoles)
                .WithOne(e => e.Role)
                .HasForeignKey(ur => ur.RoleId)
                .IsRequired();

            // Each Role can have many associated RoleClaims
            b.HasMany(e => e.RoleClaims)
                .WithOne(e => e.Role)
                .HasForeignKey(rc => rc.RoleId)
                .IsRequired();
        });
    }
}

Używanie kluczy złożonych

W poprzednich sekcjach pokazano zmianę typu klucza używanego Identity w modelu. Zmiana modelu kluczy na Identity używanie kluczy złożonych nie jest obsługiwana ani zalecana. Używanie klucza złożonego z Identity programem obejmuje zmianę sposobu Identity interakcji kodu menedżera z modelem. To dostosowanie wykracza poza zakres tego dokumentu.

Zmienianie nazw tabel/kolumn i aspektów

Aby zmienić nazwy tabel i kolumn, wywołaj metodę base.OnModelCreating. Następnie dodaj konfigurację, aby zastąpić dowolną wartość domyślną. Aby na przykład zmienić nazwę wszystkich Identity tabel:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.ToTable("MyUsers");
    });

    modelBuilder.Entity<IdentityUserClaim<string>>(b =>
    {
        b.ToTable("MyUserClaims");
    });

    modelBuilder.Entity<IdentityUserLogin<string>>(b =>
    {
        b.ToTable("MyUserLogins");
    });

    modelBuilder.Entity<IdentityUserToken<string>>(b =>
    {
        b.ToTable("MyUserTokens");
    });

    modelBuilder.Entity<IdentityRole>(b =>
    {
        b.ToTable("MyRoles");
    });

    modelBuilder.Entity<IdentityRoleClaim<string>>(b =>
    {
        b.ToTable("MyRoleClaims");
    });

    modelBuilder.Entity<IdentityUserRole<string>>(b =>
    {
        b.ToTable("MyUserRoles");
    });
}

W tych przykładach są używane typy domyślne Identity . Jeśli używasz typu aplikacji, takiego jak ApplicationUser, skonfiguruj ten typ zamiast typu domyślnego.

W poniższym przykładzie wprowadzono zmiany niektórych nazw kolumn:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.Property(e => e.Email).HasColumnName("EMail");
    });

    modelBuilder.Entity<IdentityUserClaim<string>>(b =>
    {
        b.Property(e => e.ClaimType).HasColumnName("CType");
        b.Property(e => e.ClaimValue).HasColumnName("CValue");
    });
}

Niektóre typy kolumn bazy danych można skonfigurować z określonymi aspektami (na przykład dozwoloną maksymalną string długością). W poniższym przykładzie ustawiono maksymalną długość kolumny dla kilku string właściwości w modelu:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<IdentityUser>(b =>
    {
        b.Property(u => u.UserName).HasMaxLength(128);
        b.Property(u => u.NormalizedUserName).HasMaxLength(128);
        b.Property(u => u.Email).HasMaxLength(128);
        b.Property(u => u.NormalizedEmail).HasMaxLength(128);
    });

    modelBuilder.Entity<IdentityUserToken<string>>(b =>
    {
        b.Property(t => t.LoginProvider).HasMaxLength(128);
        b.Property(t => t.Name).HasMaxLength(128);
    });
}

Mapuj na inny schemat

Schematy mogą zachowywać się inaczej w przypadku dostawców baz danych. W przypadku programu SQL Server wartością domyślną jest utworzenie wszystkich tabel w schemacie dbo . Tabele można tworzyć w innym schemacie. Na przykład:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.HasDefaultSchema("notdbo");
}

Ładowanie opóźnione

W tej sekcji dodano obsługę leniwych serwerów proxy w Identity modelu. Ładowanie leniwe jest przydatne, ponieważ umożliwia korzystanie z właściwości nawigacji bez uprzedniego upewnienia się, że są ładowane.

Typy jednostek mogą być odpowiednie do ładowania z opóźnieniem na kilka sposobów, zgodnie z opisem EF Core w dokumentacji. Dla uproszczenia należy używać serwerów proxy ładujących z opóźnieniem, co wymaga:

W poniższym przykładzie pokazano wywołanie metody UseLazyLoadingProxies w pliku Startup.ConfigureServices:

services
    .AddDbContext<ApplicationDbContext>(
        b => b.UseSqlServer(connectionString)
              .UseLazyLoadingProxies())
    .AddDefaultIdentity<ApplicationUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Zapoznaj się z poprzednimi przykładami, aby uzyskać wskazówki dotyczące dodawania właściwości nawigacji do typów jednostek.

Dodatkowe zasoby