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:
- Definiowanie lub aktualizowanie modelu danych w kodzie.
- Dodaj migrację, aby przetłumaczyć ten model na zmiany, które można zastosować do bazy danych.
- Sprawdź, czy migracja poprawnie reprezentuje twoje intencje.
- Zastosuj migrację, aby zaktualizować bazę danych do synchronizacji z modelem.
- 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ć wieleUserClaims
elementów . - Każdy
User
może mieć wieleUserLogins
elementów . - Każdy
User
może mieć wieleUserTokens
elementów . - Każdy
Role
może mieć wiele skojarzonych elementówRoleClaims
. - Każda może mieć wiele skojarzonych elementów
Roles
, a każdaUser
z nichRole
może być skojarzona z wielomaUsers
. Jest to relacja wiele-do-wielu, która wymaga tabeli sprzężenia w bazie danych. Tabela sprzężenia jest reprezentowanaUserRole
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 OnModelCreating
base.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 IdentityDbContext
klasy , AddEntityFrameworkStores
może nie wywnioskować poprawnych typów POCO dla TUserClaim
, TUserLogin
i 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 IdentityUser
elementu . Nazwa tego typu ApplicationUser
jest 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 OnModelCreating
ApplicationDbContext
. 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:
Jeśli baza danych została utworzona przed zmianą PK, uruchom polecenie
Drop-Database
(PMC) lubdotnet ef database drop
(interfejs wiersza polecenia platformy .NET), aby go usunąć.Po potwierdzeniu usunięcia bazy danych usuń migrację początkową za pomocą
Remove-Migration
(PMC) lubdotnet ef migrations remove
(interfejs wiersza polecenia platformy .NET).Zaktualizuj klasę
ApplicationDbContext
, aby pochodziła z IdentityDbContext<TUser,TRole,TKey>klasy . Określ nowy typ klucza dla elementuTKey
. 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>();
Jeśli jest używana klasa niestandardowa
ApplicationUser
, zaktualizuj klasę, aby dziedziczyła zIdentityUser
klasy . 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 niestandardowejApplicationUser
: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
.Jeśli jest używana klasa niestandardowa
ApplicationRole
, zaktualizuj klasę, aby dziedziczyła zIdentityRole<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 niestandardowejApplicationRole
. Na przykład następująca klasa odwołuje się do niestandardowegoApplicationUser
i niestandardowegoApplicationRole
elementu :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 TKey
IdentityUserClaim<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 zamiastIdentity{...}
typów. - Pamiętaj, aby użyć elementu
Application{...}
w definicji ogólnejApplicationContext
.
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:
- Instalacja pakietu Microsoft.EntityFrameworkCore.Proxies.
- Wywołanie do UseLazyLoadingProxies wewnątrz AddDbContextelementu .
- Typy jednostek publicznych z właściwościami
public virtual
nawigacji.
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.
Ostrzeżenie
W tym artykule przedstawiono użycie parametry połączenia. W przypadku lokalnej bazy danych użytkownik nie musi być uwierzytelniany, ale w środowisku produkcyjnym parametry połączenia czasami zawiera hasło do uwierzytelniania. Poświadczenie hasła właściciela zasobu (ROPC) jest zagrożeniem bezpieczeństwa, którego należy unikać w produkcyjnych bazach danych. Aplikacje produkcyjne powinny korzystać z najbezpieczniejszego dostępnego przepływu uwierzytelniania. Aby uzyskać więcej informacji na temat uwierzytelniania aplikacji wdrożonych w środowiskach testowych lub produkcyjnych, zobacz Bezpieczne przepływy uwierzytelniania.