ASP.NET Core에서 Identity 모델 사용자 지정

작성자: Arthur Vickers

ASP.NET Core Identity는 ASP.NET Core 앱에서 사용자 계정을 관리하고 저장하기 위한 프레임워크를 제공합니다. Identity는 개별 사용자 계정이 인증 메커니즘으로 선택된 경우 프로젝트에 추가됩니다. 기본적으로 Identity에서는 EF(Entity Framework) Core 데이터 모델을 사용합니다. 이 문서에서는 Identity 모델을 사용자 정의하는 방법을 설명합니다.

Identity 및 EF Core 마이그레이션

모델을 검사하기 전에 Identity에서 EF Core 마이그레이션을 사용하여 데이터베이스를 만들고 업데이트하는 방법을 이해하는 것이 유용합니다. 최상위 수준에서 프로세스는 다음과 같습니다.

  1. 코드에서 데이터 모델을 정의하거나 업데이트합니다.
  2. 마이그레이션을 추가하여 이 모델을 데이터베이스에 적용할 수 있는 변경 내용으로 변환합니다.
  3. 마이그레이션이 제대로 진행되는지 확인합니다.
  4. 마이그레이션을 적용하여 데이터베이스를 모델과 동기화되도록 업데이트합니다.
  5. 1 ~ 4단계를 반복하여 모델을 구체화하고 데이터베이스를 동기화된 상태로 유지합니다.

다음 방법 중 하나를 사용하여 마이그레이션을 추가하고 적용합니다.

  • Visual Studio를 사용하는 경우 PMC(패키지 관리자 콘솔) 창. 자세한 내용은 EF CorePMC 도구를 참조하세요.
  • 명령줄을 사용하는 경우 .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 클래스는 제네릭이며, 이는 모델에 있는 하나 이상의 엔터티 형식에 대해 서로 다른 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 템플릿에서 생성됩니다.

컨텍스트는 다음과 같은 두 가지 방법으로 모델을 구성하는 데 사용됩니다.

  • 제네릭 형식 매개 변수에 대한 엔터티 및 키 유형 제공.
  • OnModelCreating을 재정의하여 이러한 형식의 매핑을 수정.

OnModelCreating을 재정의할 때 base.OnModelCreating을 먼저 호출해야 합니다. 재정의 구성은 다음에 호출해야 합니다. 일반적으로 EF Core는 구성에 대해 최신 항목 사용(last-one-wins) 정책을 사용합니다. 예를 들어 엔터티 형식에 대한 ToTable 메서드가 먼저 한 테이블 이름으로 호출된 다음, 나중에 다른 테이블 이름으로 다시 호출되는 경우 두 번째 호출의 테이블 이름이 사용됩니다.

참고: DbContextIdentityDbContext에서 파생되지 않으면 AddEntityFrameworkStoresTUserClaim, TUserLoginTUserToken에 대해 올바른 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은 인수 없이 호출되어 탐색 속성 없이 관계를 만듭니다.

연결된 UserClaims를 사용자가 참조할 수 있도록 허용하는 탐색 속성을 ApplicationUser에 추가합니다.

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

IdentityUserClaim<TKey>에 대한 TKey는 사용자의 PK에 대해 지정된 형식입니다. 이 경우 TKey는 기본값이 사용되므로 string입니다. 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가 변경되지 않았으므로 이러한 종류의 모델 변경은 데이터베이스를 업데이트할 필요가 없습니다. 변경한 후 마이그레이션을 추가하여 확인할 수 있습니다. UpDown 메서드가 비어 있습니다.

모든 사용자 탐색 속성 추가

다음 예제에서는 위의 섹션을 지침으로 사용하여 사용자의 모든 관계에 대한 단방향 탐색 속성을 구성합니다.

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

엔터티 형식에 탐색 속성을 추가하는 지침은 앞의 예제를 참조하세요.

추가 리소스