ASP.NET Core での Identity モデルのカスタマイズ

作成者: Arthur Vickers

ASP.NET Core Identity は、ASP.NET Core アプリでユーザー アカウントを管理および格納するためのフレームワークを提供します。 認証メカニズムとして [個人のユーザー アカウント] を選択すると、Identity がプロジェクトに追加されます。 Identity では、既定により、Entity Framework (EF) Core データ モデルが使用されます。 この記事では、Identity モデルをカスタマイズする方法について説明します。

Identity と EF Core の移行

モデルを調べる前に、Identity と EF Core 移行がどのように連携してデータベースを作成および更新するかを理解しておくと役立ちます。 トップ レベルのプロセスは、次のとおりです。

  1. コードでデータ モデルを定義または更新します。
  2. 移行を追加して、このモデルを、データベースに適用できる変更に変換します。
  3. 移行が意図を正しく表していることを確認します。
  4. 移行を適用して、モデルと同期するようにデータベースを更新します。
  5. 手順 1 から 4 を繰り返して、モデルをさらに調整し、データベースの同期を維持します。

移行を追加して適用するには、次のいずれかの方法を使用します。

  • Visual Studio を使用する場合は、パッケージ マネージャー コンソール (PMC) ウィンドウ。 詳細については、EF Core PMC ツールに関する記事を参照してください。
  • コマンド ラインを使用する場合は、.NET Core CLI。 詳細については、EF Core .NET コマンド ライン ツールに関する記事を参照してください。
  • アプリの実行時は、エラー ページの [移行の適用] ボタンをクリックします。

ASP.NET Core には、開発時エラー ページ ハンドラーがあります。 このハンドラーを使用すると、アプリの実行時に移行を適用できます。 通常、運用アプリでは、移行から SQL スクリプトを生成し、制御されたアプリとデータベースのデプロイの一部としてデータベースの変更をデプロイします。

Identity を使用する新しいアプリを作成する場合、上記の手順 1 と 2 は既に完了しています。 つまり、初期データ モデルは既に存在し、初期移行はプロジェクトに追加されています。 その場合も、初期移行をデータベースに追加する必要があります。 初期移行は、次のいずれかの方法で適用することができます。

  • PMC で Update-Database を実行する。
  • コマンド シェルで dotnet ef database update を実行する。
  • アプリの実行時は、エラー ページの [移行の適用] ボタンをクリックする。

モデルに変更が加えられたら、前述の手順を繰り返します。

Identity モデル

エンティティの種類

Identity モデルは、次のエンティティ型で構成されます。

エンティティ型 説明
User ユーザーを表します。
Role ロールを表します。
UserClaim ユーザーが所有するクレームを表します。
UserToken ユーザーの認証トークンを表します。
UserLogin ユーザーをログインに関連付けます。
RoleClaim ロール内のすべてのユーザーに付与されるクレームを表します。
UserRole ユーザーとロールを関連付ける結合エンティティ。

エンティティ型のリレーションシップ

エンティティ型は、次の方法で相互に関連付けられます。

  • User には、多くの UserClaims を含めることができます。
  • User には、多くの UserLogins を含めることができます。
  • User には、多くの UserTokens を含めることができます。
  • Role には、多くの RoleClaims を関連付けることができます。
  • User には、多くの Roles を関連付けることができ、Role には、多くの Users を関連付けることができます。 これは、データベース内の結合テーブルを必要とする多対多リレーションシップです。 結合テーブルは、UserRole エンティティで表されます。

既定のモデル構成

Identity を使用すると、モデルを構成して使用するために、DbContext から継承される "コンテキスト クラス" が定義されます。 この構成は、コンテキスト クラスの OnModelCreating メソッドで EF Core Code First Fluent API を使用して行います。 既定の構成は次のとおりです。

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

モデルのジェネリック型

Identity では、上記の各エンティティ型に対して既定の共通言語ランタイム (CLR) 型を定義します。 これらの型にはすべて、プレフィックス Identity が付けられます。

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

これらの型は、直接使用するのではなく、アプリ自体の型の基底クラスとして使用できます。 Identity で定義された DbContext クラスはジェネリックであるため、モデル内の 1 つ以上のエンティティ型に異なる CLR 型を使用できます。 さらに、これらのジェネリック型を使用すると、User 主キー (PK) データ型を変更することもできます。

ロールのサポートで Identity を使用する場合、IdentityDbContext クラスを使用する必要があります。 次に例を示します。

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

ロールなし (クレームのみ) で Identity を使用することもできます。その場合、IdentityUserContext<TUser> クラスを使用する必要があります。

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

モデルをカスタマイズする

モデルのカスタマイズの開始点は、適切なコンテキスト型からの派生です。 「モデルのジェネリック型」セクションを参照してください。 通常、このコンテキスト型は、ApplicationDbContext と呼ばれており、ASP.NET Core テンプレートによって作成されます。

コンテキストは、次の 2 つの方法でモデルを構成するために使用されます。

  • ジェネリック型パラメーターのエンティティとキーの型を提供する。
  • OnModelCreating をオーバーライドして、これらの型のマッピングを変更する。

OnModelCreating をオーバーライドする場合、最初に base.OnModelCreating を呼び出し、次に、オーバーライドする構成を呼び出す必要があります。 通常、EF Core には、構成について、最後の構成が優先されるという原則があります。 たとえば、エンティティ型の ToTable メソッドが最初にあるテーブル名で呼び出され、次に別のテーブル名で再度呼び出された場合、2 番目の呼び出しのテーブル名が使用されます。

"": DbContextIdentityDbContext から派生していない場合は、AddEntityFrameworkStoresTUserClaimTUserLoginTUserToken の正しい POCO タイプを推測できないおそれがあります。 AddEntityFrameworkStores で正しい POCO タイプを推測できない場合の回避策は、services.AddScoped<IUser/RoleStore<TUser>UserStore<...>> を使って正しいタイプを直接追加することです。

カスタム ユーザー データ

カスタム ユーザー データは、IdentityUser から継承することによってサポートされます。 この型には、ApplicationUser という名前を付けるのが通例です。

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

コンテキストのジェネリック引数として ApplicationUser 型を使用します。

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

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

ApplicationDbContext クラスの OnModelCreating をオーバーライドする必要はありません。 EF Core では、規則により、CustomTag プロパティがマップされます。 ただし、新しい CustomTag 列を作成するには、データベースを更新する必要があります。 列を作成するには、移行を追加し、「Identity と EF Core 移行」の説明に従ってデータベースを更新します。

Pages/Shared/_LoginPartial.cshtml を更新して、IdentityUserApplicationUser に置き換えます。

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

Areas/Identity/IdentityHostingStartup.cs または Startup.ConfigureServices を更新して、IdentityUserApplicationUser に置き換えます。

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

AddDefaultIdentity の呼び出しは、次のコードと同じです。

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 は、Razor クラス ライブラリとして提供されます。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。 したがって、上記のコードでは、AddDefaultUI を呼び出す必要があります。 Identity のスキャフォールディングを使用して Identity ファイルをプロジェクトに追加した場合、AddDefaultUI への呼び出しを削除します。 詳細については、次を参照してください。

主キーの種類を変更する

多くのデータベースシステムでは、データベースを作成した後に PK 列のデータ型を変更すると問題が発生します。 通常、PK を変更するには、テーブルを削除してから再作成する必要があります。 そのため、データベースの作成時に、初期移行でキーの種類を指定する必要があります。

PK の種類を変更するには、次の手順に従います。

  1. PK を変更する前にデータベースを作成した場合、Drop-Database (PMC) または dotnet ef database drop (.NET Core CLI) を実行してそのデータベースを削除します。

  2. データベースが削除されたことを確認した後、Remove-Migration (PMC) または dotnet ef migrations remove (.NET Core CLI) を使用して初期移行を削除します。

  3. IdentityDbContext<TUser,TRole,TKey> から派生するように ApplicationDbContext クラスを更新します。 TKey の新しいキーの種類を指定します。 たとえば、キーの種類 Guid を使用するには、次のように入力します。

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

    上記のコードでは、ジェネリッククラス IdentityUser<TKey>IdentityRole<TKey> では、新しいキーの種類を使用するように指定する必要があります。

    ジェネリック ユーザーを使用するように Startup.ConfigureServices を更新する必要があります。

    services.AddDefaultIdentity<IdentityUser<Guid>>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();
    
  4. カスタムの ApplicationUser クラスを使用する場合、IdentityUser から継承するようにクラスを更新します。 次に例を示します。

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

    カスタムの ApplicationUser クラスを参照するように ApplicationDbContext を更新します。

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

    Startup.ConfigureServices に Identity サービスを追加するときに、カスタム データベース コンテキスト クラスを登録します。

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

    主キーのデータ型は、DbContext オブジェクトを分析することによって推定されます。

    Identity は、Razor クラス ライブラリとして提供されます。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。 したがって、上記のコードでは、AddDefaultUI を呼び出す必要があります。 Identity のスキャフォールディングを使用して Identity ファイルをプロジェクトに追加した場合、AddDefaultUI への呼び出しを削除します。

  5. カスタムの ApplicationRole クラスを使用する場合、IdentityRole<TKey> から継承するようにクラスを更新します。 次に例を示します。

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

    カスタムの ApplicationRole クラスを参照するように ApplicationDbContext を更新します。 たとえば、次のクラスでは、カスタム ApplicationUser とカスタム 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)
        {
        }
    }
    

    Startup.ConfigureServices に Identity サービスを追加するときに、カスタム データベース コンテキスト クラスを登録します。

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

    主キーのデータ型は、DbContext オブジェクトを分析することによって推定されます。

    Identity は、Razor クラス ライブラリとして提供されます。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。 したがって、上記のコードでは、AddDefaultUI を呼び出す必要があります。 Identity のスキャフォールディングを使用して Identity ファイルをプロジェクトに追加した場合、AddDefaultUI への呼び出しを削除します。

ナビゲーション プロパティを追加する

リレーションシップのモデル構成を変更することは、他の変更を行うよりも困難な場合があります。 新しい追加のリレーションシップを作成するのではなく、既存のリレーションシップを置き換えるには、注意が必要です。 特に、変更されたリレーションシップは、既存のリレーションシップと同じ外部キー (FK) プロパティを指定する必要があります。 たとえば、UsersUserClaims 間のリレーションシップは、既定で次のように指定されます。

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

このリレーションシップの FK は、UserClaim.UserId プロパティとして指定されます。 ナビゲーション プロパティを使用しないでリレーションシップを作成するには、HasManyWithOne を引数なしで呼び出します。

ナビゲーション プロパティを ApplicationUser に追加して、関連付けられた UserClaims がユーザーから参照できるようにします。

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

IdentityUserClaim<TKey>TKey は、ユーザーの PK に対して指定された型です。 この場合、既定値が使用されるため、TKeystring です。 これは、UserClaim エンティティ型の PK ではありません

ナビゲーション プロパティは存在しているので、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();
        });
    }
}

HasMany の呼び出しで指定されたナビゲーション プロパティのみを使用して、リレーションシップが以前とまったく同じように構成されていることに注意してください。

ナビゲーション プロパティは、EF モデルにのみ存在し、データベースには存在しません。 リレーションシップの FK は変更されていないため、この種のモデルの変更では、データベースを更新する必要はありません。 これは、変更を加えた後に移行を追加することで確認できます。 Up および Down メソッドは空です。

すべてのユーザー ナビゲーション プロパティを追加する

上記のセクションをガイダンスとして使用して、次の例では、ユーザーのすべてのリレーションシップに対して一方向のナビゲーション プロパティを構成します。

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

ユーザーおよびロールのナビゲーション プロパティを追加する

上記のセクションをガイダンスとして使用して、次の例では、ユーザーとロールのすべてのリレーションシップに対してナビゲーション プロパティを構成します。

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

    }
}

メモ:

  • この例には、ユーザーからロールへの多対多リレーションシップをナビゲートするために必要な UserRole 結合エンティティも含まれています。
  • Identity{...}型ではなく Application{...} 型が使用されるようになったことを反映するようにナビゲーション プロパティの型を変更することを忘れないでください。
  • ジェネリック ApplicationContext 定義で Application{...} を使用することを忘れないでください。

すべてのナビゲーション プロパティを追加する

上記のセクションをガイダンスとして使用して、次の例では、すべてのエンティティ型のすべてのリレーションシップに対してナビゲーション プロパティを構成します。

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

複合キーを使用する

前のセクションでは、Identity モデルで使用されるキーの種類の変更について説明しました。 複合キーを使用するように Identity キー モデルを変更することはサポートされていないか、推奨されません。 Identity で複合キーを使用するには、Identity マネージャー コードがモデルとやりとりする方法を変更する必要があります。 このカスタマイズは、このドキュメントでは扱いません。

テーブルおよび列の名前とファセットを変更する

テーブルおよび列の名前を変更するには、base.OnModelCreating を呼び出します。 次に、既定値をオーバーライドする構成を追加します。 たとえば、すべての Identity テーブルの名前を変更するには、次のようにします。

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

これらの例では、既定の Identity 型を使用します。 ApplicationUser などのアプリの種類を使用する場合、既定の型ではなくその型を構成します。

次の例では、一部の列名を変更します。

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

データベースの列の一部の型は、特定の "ファセット" (たとえば、許容される最大 string 長) で構成できます。 次の例では、モデル内のいくつかの string プロパティについて列の最大長を設定します。

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

異なるスキーマにマップする

スキーマは、データベース プロバイダー間で動作が異なる場合があります。 SQL Server の場合、既定では、dbo スキーマ内にすべてのテーブルが作成されます。 テーブルは、別のスキーマ内に作成される場合があります。 次に例を示します。

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

    modelBuilder.HasDefaultSchema("notdbo");
}

遅延読み込み

このセクションでは、Identity モデルでの遅延読み込みプロキシのサポートが追加されます。 遅延読み込みは、ナビゲーション プロパティが読み込まれていることを最初に確認せずに使用できるため、便利です。

EF Core のドキュメントで説明されているように、エンティティ型は、いくつかの方法で遅延読み込みに適したものにすることができます。 簡単にするために、遅延読み込みプロキシを使用します。これには次のことが必要です。

次の例では、Startup.ConfigureServices 内での UseLazyLoadingProxies メソッドの呼び出しを示しています。

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

ナビゲーション プロパティをエンティティ型に追加するためのガイダンスについては、前述の例を参照してください。

その他の技術情報