다음을 통해 공유


흐름 API - 속성과 형식 구성 및 매핑

Entity Framework Code를 사용하는 경우 Code First의 기본 동작은 EF에 구운 규칙 집합을 사용하여 POCO 클래스를 테이블에 매핑하는 것입니다. 그러나 이러한 규칙을 따를 수 없거나 따르지 않으려는 경우가 있으며, 규칙이 지시하는 것 이외의 다른 항목에 엔터티를 매핑해야 하는 경우도 있습니다.

규칙, 즉 주석 또는 EF 흐름 API 이외의 항목을 사용하도록 EF를 구성할 수 있는 두 가지 주요 방법이 있습니다. 주석은 흐름 API 기능의 하위 집합만 포함하므로 주석을 사용하여 수행할 수 없는 매핑 시나리오가 있습니다. 이 문서는 흐름 API를 사용하여 속성을 구성하는 방법을 보여줍니다.

Code First 흐름 API는 파생 DbContext에서 OnModelCreating 메서드를 재정의하여 가장 일반적으로 액세스합니다. 다음 샘플은 흐름 API를 사용하여 다양한 작업을 수행하는 방법을 보여주고 코드를 복사하여 모델에 맞게 사용자 지정할 수 있도록 설계되었습니다. 있는 그대로 사용할 수 있는 모델은 문서의 끝에 제공됩니다.

모델 전체 설정

기본 스키마(EF6 이상)

EF6부터 DbModelBuilder의 HasDefaultSchema 메서드를 사용하여 모든 테이블, 저장 프로시저 등에 사용할 데이터베이스 스키마를 지정할 수 있습니다. 이 기본 설정은 다른 스키마를 명시적으로 구성하는 모든 개체에 대해 재정의됩니다.

modelBuilder.HasDefaultSchema("sales");

사용자 지정 규칙(EF6 이상)

EF6부터 Code First에 포함된 규칙을 보완하기 위해 고유한 규칙을 만들 수 있습니다. 자세한 내용은 사용자 지정 Code First 규칙을 참조하세요.

속성 매핑

Property 메서드는 엔터티 또는 복합 형식에 속하는 각 속성에 대한 특성을 구성하는 데 사용됩니다. Property 메서드는 지정된 속성에 대한 구성 개체를 가져오는 데 사용됩니다. 구성 개체의 옵션은 구성되는 형식과 관련이 있습니다. IsUnicode는 예를 들어 문자열 속성에서만 사용할 수 있습니다.

기본 키 구성

기본 키에 대한 Entity Framework 규칙은 다음과 같습니다.

  1. 클래스는 이름이 "ID" 또는 "Id"인 속성을 정의합니다.
  2. 또는 클래스 이름 뒤에 "ID" 또는 "Id"가 붙습니다.

속성을 기본 키로 명시적으로 설정하기 위해 HasKey 메서드를 사용할 수 있습니다. 다음 예제에서는 HasKey 메서드를 사용하여 OfficeAssignment 형식에서 InstructorID 기본 키를 구성합니다.

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

복합 기본 키 구성

다음 예제에서는 DepartmentID 및 Name 속성을 Department 형식의 복합 기본 키로 구성합니다.

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

숫자 기본 키에 대한 ID 해제

다음 예제에서는 DepartmentID 속성을 System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None으로 설정하여 값이 데이터베이스에 의해 생성되지 않음을 나타냅니다.

modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

속성의 최대 길이 지정

다음 예제에서는 Name 속성이 50자를 넘지 않아야 합니다. 값을 50자 이상으로 설정하면 DbEntityValidationException 예외가 발생합니다. Code First가 이 모델에서 데이터베이스를 만드는 경우 Name 열의 최대 길이도 50자로 설정됩니다.

modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

필수 속성 구성

다음 예제에서는 Name 속성이 필요합니다. 이름을 지정하지 않으면 DbEntityValidationException 예외가 발생합니다. Code First가 이 모델에서 데이터베이스를 만드는 경우 이 속성을 저장하는 데 사용되는 열은 일반적으로 null을 허용하지 않습니다.

참고 항목

경우에 따라 속성이 필요하더라도 데이터베이스의 열이 null을 허용하지 않을 수 있습니다. 예를 들어 여러 형식에 TPH 상속 전략 데이터를 사용하는 경우 이는 단일 테이블에 저장됩니다. 파생 형식에 필수 속성이 포함된 경우 계층 구조의 모든 형식에 해당 속성이 있는 것은 아니으므로 열이 null을 허용하지 않습니다.

modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

하나 이상의 속성에서 인덱스 구성

참고 항목

EF6.1 이상만 - 인덱스 특성은 Entity Framework 6.1에서 도입되었습니다. 이전 버전을 사용하는 경우 이 섹션의 정보가 적용되지 않습니다.

인덱스 만들기는 흐름 API에서 기본적으로 지원되지 않지만 흐름 API를 통해 IndexAttribute에 대한 지원을 사용할 수 있습니다. 인덱스 특성은 모델에 모델 주석을 포함하여 처리된 다음 파이프라인의 뒷부분에서 데이터베이스의 인덱스로 전환됩니다. 흐름 API를 사용하여 이러한 동일한 주석을 수동으로 추가할 수 있습니다.

이 작업을 수행하는 가장 쉬운 방법은 새 인덱스에 대한 모든 설정을 포함하는 IndexAttribute 인스턴스를 만드는 것입니다. 그런 다음 IndexAttribute 설정을 EF 모델에 저장할 수 있는 모델 주석으로 변환하는 EF 특정 형식인 IndexAnnotation 인스턴스를 만들 수 있습니다. 그런 다음, 주석의 인덱스 이름을 지정하여 흐름 API의 HasColumnAnnotation 메서드에 전달할 수 있습니다.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

IndexAttribute에서 사용할 수 있는 설정의 전체 목록은 Code First 데이터 주석인덱스 섹션을 참조하세요. 여기에는 인덱스 이름 사용자 지정, 고유 인덱스 만들기 및 다중 열 인덱스 만들기가 포함됩니다.

IndexAttribute 배열을 IndexAnnotation의 생성자에 전달하여 단일 속성에 여러 인덱스 주석을 지정할 수 있습니다.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation(
        "Index",  
        new IndexAnnotation(new[]
            {
                new IndexAttribute("Index1"),
                new IndexAttribute("Index2") { IsUnique = true }
            })));

데이터베이스의 열에 CLR 속성을 매핑하지 않도록 지정

다음 예제에서는 CLR 형식의 속성이 데이터베이스의 열에 매핑되지 않도록 지정하는 방법을 보여줍니다.

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

CLR 속성을 데이터베이스의 특정 열에 매핑

다음 예제에서는 Name CLR 속성을 DepartmentName 데이터베이스 열에 매핑합니다.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .HasColumnName("DepartmentName");

모델에 정의되지 않은 외래 키 이름 바꾸기

CLR 형식에서 외래 키를 정의하지 않고 데이터베이스에 포함할 이름을 지정하려는 경우 다음을 수행합니다.

modelBuilder.Entity<Course>()
    .HasRequired(c => c.Department)
    .WithMany(t => t.Courses)
    .Map(m => m.MapKey("ChangedDepartmentID"));

String 속성이 유니코드 콘텐츠를 지원하는지 여부 구성

기본적으로 문자열은 유니코드(SQL Server의 nvarchar)입니다. IsUnicode 메서드를 사용하여 문자열이 varchar 형식이 되도록 지정할 수 있습니다.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .IsUnicode(false);

데이터베이스 열의 데이터 형식 구성

HasColumnType 메서드를 사용하면 동일한 기본 형식의 다른 표현에 매핑할 수 있습니다. 이 메서드를 사용하면 런타임 시 데이터를 변환할 수 없습니다. IsUnicode는 데이터베이스에 구애받지 않으므로 열을 varchar로 설정하는 기본 방법입니다.

modelBuilder.Entity<Department>()   
    .Property(p => p.Name)   
    .HasColumnType("varchar");

복합 형식에서 속성 구성

복합 형식에서 스칼라 속성을 구성하는 방법에는 두 가지가 있습니다.

ComplexTypeConfiguration에서 속성을 호출할 수 있습니다.

modelBuilder.ComplexType<Details>()
    .Property(t => t.Location)
    .HasMaxLength(20);

점 표기법을 사용하여 복합 형식의 속성에 액세스할 수도 있습니다.

modelBuilder.Entity<OnsiteCourse>()
    .Property(t => t.Details.Location)
    .HasMaxLength(20);

낙관적 동시성 토큰으로 사용할 속성 구성

엔터티의 속성이 동시성 토큰을 나타내도록 지정하기 위해 ConcurrencyCheck 특성 또는 IsConcurrencyToken 메서드를 사용할 수 있습니다.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsConcurrencyToken();

IsRowVersion 메서드를 사용하여 속성을 데이터베이스의 행 버전으로 구성할 수도 있습니다. 속성이 행 버전이 되도록 설정하면 해당 속성은 낙관적 동시성 토큰이 되도록 자동으로 구성됩니다.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsRowVersion();

형식 매핑

클래스가 복합 형식임을 지정

규칙에 따라 기본 키가 지정되지 않은 형식은 복합 형식으로 처리됩니다. Code First가 복합 형식을 검색하지 않는 몇 가지 시나리오가 있습니다(예: ID라는 속성이 있지만 기본 키라는 의미는 아님). 이러한 경우 흐름 API를 사용하여 형식이 복합 형식임을 명시적으로 지정합니다.

modelBuilder.ComplexType<Details>();

CLR 엔터티 형식을 데이터베이스의 테이블에 매핑하지 않도록 지정

다음 예제에서는 데이터베이스의 테이블에 매핑되는 CLR 형식을 제외하는 방법을 보여줍니다.

modelBuilder.Ignore<OnlineCourse>();

데이터베이스의 특정 테이블에 엔터티 형식 매핑

부서의 모든 속성은 t_ Department라는 테이블의 열에 매핑됩니다.

modelBuilder.Entity<Department>()  
    .ToTable("t_Department");

다음과 같이 스키마 이름을 지정할 수도 있습니다.

modelBuilder.Entity<Department>()  
    .ToTable("t_Department", "school");

TPH(계층당 하나의 테이블) 상속 매핑

TPH 매핑 시나리오에서 상속 계층 구조의 모든 형식은 단일 테이블에 매핑됩니다. 판별자 열은 각 행의 형식을 식별하는 데 사용됩니다. Code First를 사용하여 모델을 만들 때 TPH는 상속 계층 구조에 참여하는 형식에 대한 기본 전략입니다. 기본적으로 판별자 열은 "판별자"라는 이름으로 테이블에 추가되고 계층 구조에서 각 형식의 CLR 형식 이름이 판별자 값에 사용됩니다. 흐름 API를 사용하여 기본 동작을 수정할 수 있습니다.

modelBuilder.Entity<Course>()  
    .Map<Course>(m => m.Requires("Type").HasValue("Course"))  
    .Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));

TPT(형식당 하나의 테이블) 상속 매핑

TPT 매핑 시나리오에서 모든 형식은 개별 테이블에 매핑됩니다. 기본 형식 또는 파생 형식에만 속하는 속성은 해당 형식에 매핑되는 테이블에 저장됩니다. 파생 형식에 매핑되는 테이블은 파생 테이블을 기본 테이블과 조인하는 외래 키도 저장합니다.

modelBuilder.Entity<Course>().ToTable("Course");  
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

TPC(구체적 형식당 테이블) 상속 매핑

TPC 매핑 시나리오에서는 계층 구조의 모든 비추상 형식이 개별 테이블에 매핑됩니다. 파생 클래스에 매핑되는 테이블은 데이터베이스의 기본 클래스에 매핑되는 테이블과 아무런 관계가 없습니다. 상속된 속성을 포함한 모든 클래스 속성을 해당 테이블의 열에 매핑합니다.

MapInheritedProperties 메서드를 호출하여 각 파생 형식을 구성합니다. MapInheritedProperties는 기본 클래스에서 상속된 모든 속성을 파생 클래스에 대한 테이블의 새 열로 다시 매핑합니다.

참고 항목

TPC 상속 계층 구조에 참여하는 테이블은 기본 키를 공유하지 않으므로 동일한 ID 시드를 사용하여 데이터베이스에서 생성된 값이 있는 경우 서브클래스에 매핑되는 테이블에 삽입할 때 엔터티 키가 중복됩니다. 이러한 문제를 해결하기 위해 각 테이블에 대해 다른 초기 시드 값을 지정하거나 기본 키 속성에서 ID를 해제할 수 있습니다. ID는 Code First로 작업할 때 정수 키 속성의 기본값입니다.

modelBuilder.Entity<Course>()
    .Property(c => c.CourseID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnsiteCourse");
});

modelBuilder.Entity<OnlineCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnlineCourse");
});

엔터티 형식의 속성을 데이터베이스의 여러 테이블에 매핑(엔터티 분할)

엔터티 분할을 사용하면 엔터티 형식의 속성을 여러 테이블에 분산할 수 있습니다. 다음 예제에서 Department 엔터티는 Department 및 DepartmentDetails의 두 테이블로 분할됩니다. 엔터티 분할은 Map 메서드에 대한 여러 호출을 사용하여 속성의 하위 집합을 특정 테이블에 매핑합니다.

modelBuilder.Entity<Department>()
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Name });
        m.ToTable("Department");
    })
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
        m.ToTable("DepartmentDetails");
    });

여러 엔터티 형식을 데이터베이스의 한 테이블에 매핑(테이블 분할)

다음 예제에서는 기본 키를 공유하는 두 엔터티 형식을 하나의 테이블에 매핑합니다.

modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal(t => t.Instructor);

modelBuilder.Entity<Instructor>().ToTable("Instructor");

modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

엔터티 형식을 삽입/업데이트/삭제 저장 프로시저에 매핑(EF6 이상)

EF6부터 엔터티를 매핑하여 삽입, 업데이트, 삭제 저장 프로시저를 사용할 수 있습니다. 자세한 내용은 Code First 삽입/업데이트/삭제 저장 프로시저를 참조하세요.

샘플에 사용되는 모델

다음 Code First 모델은 이 페이지의 샘플에 사용됩니다.

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;

public class SchoolEntities : DbContext
{
    public DbSet<Course> Courses { get; set; }
    public DbSet<Department> Departments { get; set; }
    public DbSet<Instructor> Instructors { get; set; }
    public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure Code First to ignore PluralizingTableName convention
        // If you keep this convention then the generated tables will have pluralized names.
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

public class Department
{
    public Department()
    {
        this.Courses = new HashSet<Course>();
    }
    // Primary key
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public decimal Budget { get; set; }
    public System.DateTime StartDate { get; set; }
    public int? Administrator { get; set; }

    // Navigation property
    public virtual ICollection<Course> Courses { get; private set; }
}

public class Course
{
    public Course()
    {
        this.Instructors = new HashSet<Instructor>();
    }
    // Primary key
    public int CourseID { get; set; }

    public string Title { get; set; }
    public int Credits { get; set; }

    // Foreign key
    public int DepartmentID { get; set; }

    // Navigation properties
    public virtual Department Department { get; set; }
    public virtual ICollection<Instructor> Instructors { get; private set; }
}

public partial class OnlineCourse : Course
{
    public string URL { get; set; }
}

public partial class OnsiteCourse : Course
{
    public OnsiteCourse()
    {
        Details = new Details();
    }

    public Details Details { get; set; }
}

public class Details
{
    public System.DateTime Time { get; set; }
    public string Location { get; set; }
    public string Days { get; set; }
}

public class Instructor
{
    public Instructor()
    {
        this.Courses = new List<Course>();
    }

    // Primary key
    public int InstructorID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public System.DateTime HireDate { get; set; }

    // Navigation properties
    public virtual ICollection<Course> Courses { get; private set; }
}

public class OfficeAssignment
{
    // Specifying InstructorID as a primary
    [Key()]
    public Int32 InstructorID { get; set; }

    public string Location { get; set; }

    // When Entity Framework sees Timestamp attribute
    // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    // Navigation property
    public virtual Instructor Instructor { get; set; }
}