Share via


Identity-Modellanpassung in ASP.NET Core

Von Arthur Vickers

ASP.NET Core Identity stellt ein Framework für die Verwaltung und Speicherung von Benutzerkonten in ASP.NET Core-Apps bereit. Identity wird Ihrem Projekt hinzugefügt, wenn als Authentifizierungsmechanismus Einzelne Benutzerkonten ausgewählt ist. Standardmäßig verwendet Identity ein Entity Framework Core-Datenmodell (EF Core). Dieser Artikel beschreibt, wie Sie das Identity-Modell anpassen können.

Identity- und EF Core-Migrationen

Bevor Sie das Modell untersuchen, ist es nützlich zu verstehen, wie Identity mithilfe von EF Core-Migrationen eine Datenbank erstellt und aktualisiert. Allgemein läuft der Prozess folgendermaßen ab:

  1. Definieren oder aktualisieren Sie ein Datenmodell im Code.
  2. Fügen Sie eine Migration hinzu, um dieses Modell in Änderungen zu übersetzen, die auf die Datenbank angewendet werden können.
  3. Überprüfen Sie, ob die Migration Ihre Absichten korrekt wiedergibt.
  4. Wenden Sie die Migration an, um die Datenbank zu aktualisieren, damit diese mit dem Modell synchron ist.
  5. Wiederholen Sie die Schritte 1 bis 4, um das Modell weiter zu verfeinern und die Datenbank synchron zu halten.

Nutzen Sie einen der folgenden Ansätze, um Migrationen hinzuzufügen und anzuwenden:

  • Das Fenster Paket-Manager-Konsole, wenn Sie Visual Studio verwenden. Weitere Informationen finden Sie unter EF Core-Tools – Paket-Manager-Konsole.
  • Die .NET Core CLI, wenn Sie die Befehlszeile verwenden. Weitere Informationen finden Sie unter EF Core-Tools – .NET Core CLI.
  • Wenn die App ausgeführt wird, klicken Sie auf der Fehlerseite auf die Schaltfläche Migrationen anwenden.

ASP.NET Core bietet zur Entwicklungszeit einen Fehlerseitenhandler. Der Handler kann Migrationen anwenden, wenn die App ausgeführt wird. Produktions-Apps generieren in der Regel SQL-Skripts aus den Migrationen und stellen Datenbankänderungen im Rahmen einer kontrollierten Bereitstellung von Apps und Datenbanken bereit.

Wenn eine neue App mit Identity erstellt wird, sind die oben genannten Schritte 1 und 2 bereits abgeschlossen. Das heißt, das ursprüngliche Datenmodell ist bereits vorhanden, und die anfängliche Migration wurde dem Projekt hinzugefügt. Die anfängliche Migration muss noch auf die Datenbank angewendet werden. Die anfängliche Migration kann auf eine der folgenden Arten angewendet werden:

  • Führen Sie in der Paket-Manager-Konsole Update-Database aus.
  • Führen Sie dotnet ef database update in einer Befehlsshell aus.
  • Wenn die App ausgeführt wird, klicken Sie auf der Fehlerseite auf die Schaltfläche Migrationen anwenden.

Wiederholen Sie die vorangegangenen Schritte, wenn Sie Änderungen am Modell vornehmen.

Das Identity-Modell

Entitätstypen

Das Identity-Modell besteht aus den folgenden Entitätstypen.

Entitätstyp Beschreibung
User Repräsentiert eine*n Benutzer*in.
Role Repräsentiert eine Rolle.
UserClaim Repräsentiert einen Anspruch, den ein*e Benutzer*in besitzt.
UserToken Repräsentiert ein Authentifizierungstoken für eine*n Benutzer*in.
UserLogin Ordnet Benutzer*innen eine Anmeldung zu.
RoleClaim Repräsentiert einen Anspruch, der allen Benutzer*innen innerhalb einer Rolle gewährt wird.
UserRole Eine Joinentität, die Benutzer*innen und Rollen zuordnet.

Entitätstypbeziehungen

Die Entitätstypen stehen in folgender Weise zueinander in Beziehung:

  • Jeder User kann über viele UserClaims verfügen.
  • Jeder User kann über viele UserLogins verfügen.
  • Jeder User kann über viele UserTokens verfügen.
  • Jeder Role können viele RoleClaims zugeordnet sein.
  • Jedem User können viele Roles zugeordnet sein, und jede Role kann vielen Users zugeordnet sein. Dies ist eine m:n-Beziehung, die eine Jointabelle in der Datenbank erfordert. Die Jointabelle wird durch die UserRole-Entität repräsentiert.

Standardmäßige Modellkonfiguration

Identity definiert viele Kontextklassen, die von DbContext erben, um das Modell zu konfigurieren und zu verwenden. Diese Konfiguration erfolgt mithilfe der EF Core-Code First-Fluent-API in der OnModelCreating-Methode der Kontextklasse. Die Standardkonfiguration lautet:

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");
});

Generische Modelltypen

Identity definiert Common Language Runtime-Standardtypen (CLR) für jeden der oben aufgeführten Entitätstypen. Diese Typen sind alle mit dem Präfix Identity versehen:

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

Anstatt diese Typen direkt zu verwenden, können die Typen als Basisklassen für die eigenen Typen der App verwendet werden. Die von Identity definierten DbContext-Klassen sind generisch, sodass verschiedene CLR-Typen für einen oder mehrere der Entitätstypen im Modell verwendet werden können. Diese generischen Typen erlauben es auch, den Datentyp des User-Primärschlüssels zu ändern.

Wenn Sie Identity mit Unterstützung für Rollen verwenden, sollten Sie eine IdentityDbContext-Klasse verwenden. Beispiel:

// 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>

Es ist auch möglich, Identity ohne Rollen (nur Ansprüche) zu verwenden. In diesem Fall sollte eine IdentityUserContext<TUser>-Klasse verwendet werden:

// 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>
{
}

Anpassen des Modells

Der Ausgangspunkt für die Modellanpassung ist die Ableitung aus dem passenden Kontexttyp. Weitere Informationen finden Sie im Abschnitt Generische Modelltypen. Dieser Kontexttyp wird üblicherweise als ApplicationDbContext bezeichnet und über die ASP.NET Core-Vorlagen erstellt.

Der Kontext wird verwendet, um das Modell auf zwei Arten zu konfigurieren:

  • Bereitstellung von Entitäts- und Schlüsseltypen für die generischen Typparameter
  • Überschreiben von OnModelCreating, um die Zuordnung dieser Typen zu ändern

Beim Überschreiben von OnModelCreating sollte zuerst base.OnModelCreating und dann die überschreibende Konfiguration aufgerufen werden. EF Core umfasst in der Regel eine Last-One-Wins-Richtlinie für die Konfiguration. Wenn zum Beispiel die ToTable-Methode für einen Entitätstyp das erste Mal mit einem Tabellennamen und später mit einem anderen Tabellennamen aufgerufen wird, wird der Tabellenname im zweiten Aufruf verwendet.

HINWEIS: Wenn DbContext nicht von IdentityDbContext abgeleitet ist, kann AddEntityFrameworkStores nicht die richtigen POCO-Typen für TUserClaim, TUserLogin und TUserToken ableiten. Wenn AddEntityFrameworkStores nicht die richtigen POCO-Typen ableitet, besteht eine Problemumgehung darin, die richtigen Typen direkt über services.AddScoped<IUser/RoleStore<TUser> und UserStore<...>> hinzuzufügen.

Benutzerdefinierte Benutzerdaten

Benutzerdefinierte Benutzerdaten werden durch Vererbung von IdentityUser unterstützt. Es ist üblich, diesen Typ ApplicationUser zu nennen:

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

Verwenden Sie den Typ ApplicationUser als generisches Argument für den Kontext:

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

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

Es ist nicht erforderlich, OnModelCreating in der Klasse ApplicationDbContext zu überschreiben. EF Core ordnet die CustomTag-Eigenschaft per Konvention zu. Die Datenbank muss jedoch aktualisiert werden, um eine neue CustomTag-Spalte zu erstellen. Zum Erstellen der Spalte fügen Sie eine Migration hinzu und aktualisieren dann die Datenbank, wie in Identity- und EF Core-Migrationen beschrieben.

Aktualisieren Sie Pages/Shared/_LoginPartial.cshtml, und ersetzen Sie IdentityUser durch ApplicationUser:

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

Aktualisieren Sie Areas/Identity/IdentityHostingStartup.cs oder Startup.ConfigureServices, und ersetzen Sie IdentityUser durch ApplicationUser.

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

Der Aufruf von AddDefaultIdentity entspricht dem folgenden Code:

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 wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von AddDefaultUI. Weitere Informationen finden Sie unter:

Ändern des Primärschlüsseltyps

Eine Änderung des Datentyps der Primärschlüsselspalte nach Erstellung der Datenbank ist bei vielen Datenbanksystemen problematisch. Wenn Sie den Primärschlüssel ändern, müssen Sie die Tabelle normalerweise löschen und neu erstellen. Daher sollten die Schlüsseltypen bei der anfänglichen Migration angegeben werden, wenn die Datenbank erstellt wird.

Führen Sie diese Schritte aus, um den Primärschlüsseltyp zu ändern:

  1. Wenn die Datenbank vor der Änderung des Primärschlüssels erstellt wurde, führen Sie Drop-Database (PMC) oder dotnet ef database drop (.NET Core CLI) aus, um sie zu löschen.

  2. Nachdem Sie die Löschung der Datenbank bestätigt haben, entfernen Sie die ursprüngliche Migration mit Remove-Migration (PMC) oder dotnet ef migrations remove (.NET Core CLI).

  3. Aktualisieren Sie die ApplicationDbContext-Klasse, um sie von IdentityDbContext<TUser,TRole,TKey> abzuleiten. Geben Sie den neuen Schlüsseltyp für TKey an. Wenn Sie zum Beispiel einen Guid-Schlüsseltyp verwenden möchten:

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

    Im vorangehenden Code müssen die generischen Klassen IdentityUser<TKey> und IdentityRole<TKey> angegeben werden, um den neuen Schlüsseltyp zu verwenden.

    Startup.ConfigureServices muss aktualisiert werden, um den generischen Benutzer zu verwenden:

    services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    
  4. Wenn eine benutzerdefinierte ApplicationUser-Klasse verwendet wird, aktualisieren Sie die Klasse, damit sie von IdentityUser erbt. Beispiel:

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

    Aktualisieren Sie ApplicationDbContext, um auf die benutzerdefinierte Klasse ApplicationUser zu verweisen:

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

    Registrieren Sie die benutzerdefinierte Datenbankkontextklasse, wenn Sie den Dienst Identity in Startup.ConfigureServices hinzufügen:

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

    Der Datentyp des Primärschlüssels wird durch Analyse des DbContext-Objekts abgeleitet.

    Identity wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von AddDefaultUI.

  5. Wenn eine benutzerdefinierte ApplicationRole-Klasse verwendet wird, aktualisieren Sie die Klasse, damit sie von IdentityRole<TKey> erbt. Beispiel:

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

    Aktualisieren Sie ApplicationDbContext, um auf die benutzerdefinierte Klasse ApplicationRole zu verweisen. Die folgende Klasse verweist beispielsweise auf einen benutzerdefinierten ApplicationUser und eine benutzerdefinierte ApplicationRole:

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

    Registrieren Sie die benutzerdefinierte Datenbankkontextklasse, wenn Sie den Dienst Identity in Startup.ConfigureServices hinzufügen:

    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);
    }
    

    Der Datentyp des Primärschlüssels wird durch Analyse des DbContext-Objekts abgeleitet.

    Identity wird als Razor-Klassenbibliothek bereitgestellt. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten. Folglich wird im vorstehenden Code ein Aufruf von AddDefaultUI benötigt. Wenn der Identity-Gerüstbau zum Hinzufügen von Identity-Dateien zum Projekt verwendet wurde, entfernen Sie den Aufruf von AddDefaultUI.

Hinzufügen von Navigationseigenschaften

Das Ändern der Modellkonfiguration im Hinblick auf Beziehungen kann schwieriger sein als andere Änderungen. Achten Sie darauf, die vorhandenen Beziehungen zu ersetzen, anstatt neue, zusätzliche Beziehungen zu schaffen. Insbesondere muss die geänderte Beziehung die gleiche Fremdschlüsseigenschaft aufweisen wie die bestehende Beziehung. Beispielsweise wird die Beziehung zwischen Users und UserClaims standardmäßig wie folgt angegeben:

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

Der Fremdschlüssel für diese Beziehung wird als UserClaim.UserId-Eigenschaft angegeben. HasMany und WithOne werden ohne Argumente aufgerufen, um die Beziehung ohne Navigationseigenschaften zu erstellen.

Fügen Sie eine Navigationseigenschaft zu ApplicationUser hinzu, die es ermöglicht, von Benutzer*innen auf zugehörige UserClaims zu verweisen:

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

Der TKey für IdentityUserClaim<TKey> ist der Typ, der für den Primärschlüssel der Benutzer*innen angegeben wird. In diesem Fall weist TKey den Typ string auf, weil die Standardwerte verwendet werden. Dies ist nicht der Primärschlüsseltyp für den UserClaim-Entitätstyp.

Da die Navigationseigenschaft nun vorhanden ist, muss sie in OnModelCreating konfiguriert werden:

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();
        });
    }
}

Beachten Sie, dass die Beziehung genau wie zuvor konfiguriert ist, nur mit einer Navigationseigenschaft, die im Aufruf von HasMany angegeben wird.

Die Navigationseigenschaften sind nur im EF-Modell vorhanden, nicht in der Datenbank. Da sich der Fremdschlüssel für die Beziehung nicht geändert hat, muss bei dieser Art von Modelländerung die Datenbank nicht aktualisiert werden. Sie können dies überprüfen, indem Sie nach der Änderung eine Migration hinzufügen. Die Methoden Up und Down sind leer.

Hinzufügen aller Benutzernavigationseigenschaften

Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel unidirektionale Navigationseigenschaften für alle Beziehungen für Benutzer*innen:

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();
        });
    }
}

Hinzufügen von Benutzer- und Rollennavigationseigenschaften

Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel Navigationseigenschaften für alle Beziehungen für Benutzer*innen und Rollen:

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();
        });

    }
}

Hinweise:

  • Dieses Beispiel umfasst auch die Joinentität UserRole, die zum Navigieren durch die m:n-Beziehung zwischen Benutzer*innen und Rollen benötigt wird.
  • Denken Sie daran, die Typen der Navigationseigenschaften so zu ändern, dass sie widerspiegeln, dass jetzt Application{...}-Typen anstelle von Identity{...}-Typen verwendet werden.
  • Denken Sie daran, Application{...} in der generischen Definition von ApplicationContext zu verwenden.

Hinzufügen aller Navigationseigenschaften

Ausgehend vom obigen Abschnitt konfiguriert das folgende Beispiel Navigationseigenschaften für alle Beziehungen für alle Entitätstypen:

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();
        });
    }
}

Verwenden zusammengesetzter Schlüssel

In den vorangegangenen Abschnitten wurde gezeigt, wie Sie den im Identity-Modell verwendeten Schlüsseltyp ändern können. Das Ändern des Identity-Schlüsselmodells zur Verwendung von zusammengesetzten Schlüsseln wird weder unterstützt noch empfohlen. Wenn Sie einen zusammengesetzten Schlüssel mit Identity verwenden, müssen Sie die Art und Weise der Interaktion zwischen dem Identity-Manager-Code und dem Modell ändern. Diese Anpassung liegt außerhalb des Rahmens dieses Dokuments.

Ändern von Tabellen-/Spaltennamen und Facetten

Rufen Sie base.OnModelCreating auf, um die Namen von Tabellen und Spalten zu ändern. Fügen Sie dann eine Konfiguration hinzu, um einen beliebigen der Standardwerte zu überschreiben. So ändern Sie beispielsweise den Namen aller Identity-Tabellen:

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");
    });
}

In diesen Beispielen werden die Identity-Standardtypen verwendet. Wenn Sie einen App-Typ wie ApplicationUser verwenden, konfigurieren Sie diesen Typ anstelle des Standardtyps.

Im folgenden Beispiel werden einige Spaltennamen geändert:

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");
    });
}

Einige Typen für Datenbankspalten können mit bestimmten Facetten konfiguriert werden (z. B. mit der maximal zulässige string-Länge). Im folgenden Beispiel werden die maximalen Spaltenlängen für mehrere string-Eigenschaften im Modell festgelegt:

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);
    });
}

Zuordnung zu einem anderen Schema

Schemas können sich bei verschiedenen Datenbankanbietern unterschiedlich verhalten. Bei SQL Server werden standardmäßig alle Tabellen im Schema dbo erstellt. Die Tabellen können in einem anderen Schema erstellt werden. Beispiel:

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

    modelBuilder.HasDefaultSchema("notdbo");
}

Lazy Loading

In diesem Abschnitt wird die Unterstützung für Lazy-Loading-Proxys (verzögertes Laden) im Identity-Modell hinzugefügt. Das Lazy Loading ist nützlich, da es die Verwendung von Navigationseigenschaften ermöglicht, ohne vorher sicherzustellen, dass sie geladen sind.

Entitätstypen können auf verschiedene Weise vorbereitet werden, um das Lazy Loading zu unterstützen, wie in der EF Core-Dokumentation beschrieben. Der Einfachheit halber verwenden Sie Lazy-Loading-Proxys, was Folgendes voraussetzt:

Das folgende Beispiel veranschaulicht den Aufruf von UseLazyLoadingProxies in Startup.ConfigureServices:

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

In den vorangegangenen Beispielen finden Sie Anleitungen zum Hinzufügen von Navigationseigenschaften zu den Entitätstypen.

Zusätzliche Ressourcen