Code First 데이터 주석

참고 항목

EF4.1 이상만 - 이 페이지에서 다루는 기능, API 등은 Entity Framework 4.1에 도입되었습니다. 이전 버전을 사용하는 경우 이 정보의 일부 또는 전체가 적용되지 않습니다.

이 페이지의 콘텐츠는 원래 Julie Lerman(<http://thedatafarm.com>)이 작성한 문서에서 적용됩니다.

Entity Framework Code First를 사용하면 사용자 고유의 도메인 클래스를 사용하여 EF가 쿼리, 변경 내용 추적 및 업데이트 함수를 수행하는 데 사용하는 모델을 나타낼 수 있습니다. Code First는 '구성에 대한 규칙'이라고 하는 프로그래밍 패턴을 활용합니다. Code First는 클래스가 Entity Framework의 규칙을 따른다고 가정하며, 이 경우 해당 작업을 수행하는 방법을 자동으로 계산합니다. 그러나 클래스가 이러한 규칙을 따르지 않는 경우 클래스에 구성을 추가하여 EF에 필수 정보를 제공할 수 있는 기능이 있습니다.

Code First는 이러한 구성을 클래스에 추가하는 두 가지 방법을 제공합니다. 하나는 DataAnnotations라는 간단한 특성을 사용하고, 두 번째는 코드에서 구성을 명령적으로 설명하는 방법을 제공하는 Code First의 흐름 API를 사용하는 것입니다.

이 문서에서는 DataAnnotations(System.ComponentModel.DataAnnotations 네임스페이스의)를 사용하여 클래스를 구성하는 데 중점을 두고 가장 일반적으로 필요한 구성을 강조 표시합니다. DataAnnotations는 이러한 애플리케이션이 클라이언트 측 유효성 검사에 동일한 주석을 활용할 수 있도록 하는 ASP.NET MVC와 같은 여러 .NET 애플리케이션에서도 이해됩니다.

모델

간단한 클래스 쌍인 블로그 및 게시물을 사용하여 Code First DataAnnotations를 보여줍니다.

    public class Blog
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public DateTime DateCreated { get; set; }
        public string Content { get; set; }
        public int BlogId { get; set; }
        public ICollection<Comment> Comments { get; set; }
    }

블로그 및 게시물 클래스는 code first 규칙을 편리하게 따르며 EF 호환성을 사용하도록 설정하기 위해 조정이 필요하지 않습니다. 그러나 주석을 사용하여 EF에 매핑되는 클래스 및 데이터베이스에 대한 자세한 정보를 제공할 수도 있습니다.

 

Key

Entity Framework는 엔터티 추적에 사용되는 키 값이 있는 모든 엔터티를 사용합니다. Code First의 한 가지 규칙은 암시적 키 속성입니다. Code First는 "Id"라는 속성 또는 클래스 이름과 "Id"(예: "BlogId")의 조합을 찾습니다. 이 속성은 데이터베이스의 기본 키 열에 매핑됩니다.

블로그 및 게시 클래스는 모두 이 규칙을 따릅니다. 그렇지 않다면 어떨까요? 블로그에서 PrimaryTrackingKey라는 이름을 대신 사용한 경우 또는 foo인 경우 어떻게 될까요? Code first가 이 규칙과 일치하는 속성을 찾지 못하면 키 속성이 있어야 한다는 Entity Framework의 요구 사항 때문에 예외가 throw됩니다. 키 주석을 사용하여 EntityKey로 사용할 속성을 지정할 수 있습니다.

    public class Blog
    {
        [Key]
        public int PrimaryTrackingKey { get; set; }
        public string Title { get; set; }
        public string BloggerName { get; set;}
        public virtual ICollection<Post> Posts { get; set; }
    }

Code first 데이터베이스 생성 기능을 사용하는 경우 블로그 테이블에는 PrimaryTrackingKey라는 기본 키 열이 있으며, 이 열은 기본적으로 ID로 정의됩니다.

Blog table with primary key

복합 키

Entity Framework는 둘 이상의 속성으로 구성된 기본 키인 복합 키를 지원합니다. 예를 들어 기본 키가 PassportNumber 및 IssuingCountry의 조합인 Passport 클래스가 있을 수 있습니다.

    public class Passport
    {
        [Key]
        public int PassportNumber { get; set; }
        [Key]
        public string IssuingCountry { get; set; }
        public DateTime Issued { get; set; }
        public DateTime Expires { get; set; }
    }

EF 모델에서 위의 클래스를 사용하려고 하면 다음과 같은 InvalidOperationException이 발생합니다.

'Passport' 형식의 복합 기본 키 순서를 확인할 수 없습니다. ColumnAttribute 또는 HasKey 메서드를 사용하여 복합 기본 키에 대한 순서를 지정합니다.

복합 키를 사용하려면 Entity Framework에서 키 속성에 대한 순서를 정의해야 합니다. 열 주석을 통해 순서를 지정하여 이 작업을 수행할 수 있습니다.

참고 항목

순서 값은 인덱스 기반이 아닌 상대 값이므로 모든 값을 사용할 수 있습니다. 예를 들어 1과 2 대신 100 및 200이 허용됩니다.

    public class Passport
    {
        [Key]
        [Column(Order=1)]
        public int PassportNumber { get; set; }
        [Key]
        [Column(Order = 2)]
        public string IssuingCountry { get; set; }
        public DateTime Issued { get; set; }
        public DateTime Expires { get; set; }
    }

복합 외래 키가 있는 엔터티가 있는 경우 해당 기본 키 속성에 사용한 것과 동일한 열 순서를 지정해야 합니다.

외래 키 속성 내의 상대 순서만 동일해야 하며 순서에 할당된 정확한 값은 일치하지 않아도 됩니다. 예를 들어 다음 클래스에서는 1과 2 대신 3과 4를 사용할 수 있습니다.

    public class PassportStamp
    {
        [Key]
        public int StampId { get; set; }
        public DateTime Stamped { get; set; }
        public string StampingCountry { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 1)]
        public int PassportNumber { get; set; }

        [ForeignKey("Passport")]
        [Column(Order = 2)]
        public string IssuingCountry { get; set; }

        public Passport Passport { get; set; }
    }

Required

Required 주석은 특정 속성이 필요하다는 것을 EF에 알릴 수 있습니다.

제목 속성에 필수를 추가하면 EF(및 MVC)가 속성에 데이터가 있는지 확인합니다.

    [Required]
    public string Title { get; set; }

애플리케이션에서 추가 코드 또는 표시 변경 없이 MVC 애플리케이션은 클라이언트 측 유효성 검사를 수행하며 속성 및 주석 이름을 사용하여 메시지를 동적으로 빌드합니다.

Create page with Title is required error

필수 특성은 매핑된 속성을 null을 허용하지 않도록 표시하여 생성된 데이터베이스에도 영향을 줍니다. 제목 필드가 "null이 아님"으로 변경되었습니다.

참고 항목

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

 

Blogs table

 

MaxLength 및 MinLength

MaxLengthMinLength 특성을 사용하면 Required와 마찬가지로 추가 속성 유효성 검사를 지정할 수 있습니다.

다음은 길이 요구 사항이 있는 BloggerName입니다. 이 예제에서는 특성을 결합하는 방법도 보여줍니다.

    [MaxLength(10),MinLength(5)]
    public string BloggerName { get; set; }

MaxLength 주석은 속성의 길이를 10으로 설정하여 데이터베이스에 영향을 미칩니다.

Blogs table showing max length on BloggerName column

MVC 클라이언트 측 주석과 EF 4.1 서버 측 주석은 모두 이 유효성 검사를 적용하며, "필드 BloggerName은 최대 길이가 '10'인 문자열 또는 배열 형식이어야 함" 오류 메시지를 동적으로 작성합니다. 해당 메시지는 조금 깁니다. 많은 주석을 사용하면 ErrorMessage 특성으로 오류 메시지를 지정할 수 있습니다.

    [MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

필수 주석에서 ErrorMessage를 지정할 수도 있습니다.

Create page with custom error message

 

NotMapped

Code first 규칙은 지원되는 데이터 형식의 모든 속성이 데이터베이스에 표시되도록 지정합니다. 하지만 애플리케이션에서 항상 그렇지는 않습니다. 예를 들어 블로그 클래스에 제목 및 BloggerName 필드를 기반으로 코드를 만드는 속성이 있을 수 있습니다. 해당 속성을 동적으로 만들 수 있으며 저장할 필요가 없습니다. 이 BlogCode 속성과 같은 NotMapped 주석을 사용하여 데이터베이스에 매핑되지 않는 속성을 표시할 수 있습니다.

    [NotMapped]
    public string BlogCode
    {
        get
        {
            return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
        }
    }

 

ComplexType

클래스 집합에서 도메인 엔터티를 설명한 다음 해당 클래스를 계층화하여 전체 엔터티를 설명하는 것은 드문 일이 아닙니다. 예를 들어 BlogDetails라는 클래스를 모델에 추가할 수 있습니다.

    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

BlogDetails에는 키 속성의 형식이 없습니다. 도메인 기반 디자인에서 BlogDetails을 값 개체라고 합니다. Entity Framework는 값 개체를 복합 형식이라고 합니다.  복합 형식은 자체 추적할 수 없습니다.

그러나 Blog 클래스의 속성인 BlogDetailsBlog 개체의 일부로 추적됩니다. 코드가 이를 먼저 인식하려면 BlogDetails 클래스를 ComplexType으로 표시해야 합니다.

    [ComplexType]
    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

이제 Blog 클래스에 속성을 추가하여 해당 블로그의 BlogDetails를 나타낼 수 있습니다.

        public BlogDetails BlogDetail { get; set; }

데이터베이스에서 Blog 테이블에는 해당 BlogDetail 속성에 포함된 속성을 포함하여 블로그의 모든 속성이 포함됩니다. 기본적으로 각 유형 앞에는 복합 형식의 이름이 "BlogDetail"입니다.

Blog table with complex type

ConcurrencyCheck

ConcurrencyCheck 주석을 사용하면 사용자가 엔터티를 편집하거나 삭제할 때 데이터베이스에서 동시성 검사에 사용할 하나 이상의 속성에 플래그를 지정할 수 있습니다. EF 디자이너로 작업한 경우 속성 ConcurrencyModeFixed로 설정하는 데 맞춰 조정됩니다.

BloggerName 속성에 추가하여 ConcurrencyCheck 작동 방식을 살펴보겠습니다.

    [ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

SaveChanges가 호출되면 BloggerName 필드의 ConcurrencyCheck 주석으로 인해 해당 속성의 원래 값이 업데이트에 사용됩니다. 명령은 키 값뿐만 아니라 의 원래 값 BloggerName에서도 필터링하여 올바른 행을 찾으려고 시도합니다.  다음은 데이터베이스로 전송된 UPDATE 명령의 중요한 부분입니다. 여기서 명령은 PrimaryTrackingKey가 1인 행과 해당 블로그가 데이터베이스에서 검색될 때 원래 값이었던 "Julie"의 BloggerName 행을 업데이트하는 것을 볼 수 있습니다.

    where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
    @4=1,@5=N'Julie'

그 동안 누군가가 해당 블로그의 블로거 이름을 변경한 경우 이 업데이트가 실패하고 처리해야 하는 DbUpdateConcurrencyException이 표시됩니다.

 

TimeStamp

동시성 검사에 rowversion 또는 타임스탬프 필드를 사용하는 것이 더 일반적입니다. 그러나 ConcurrencyCheck 주석을 사용하는 대신 속성의 형식이 바이트 배열인 한 더 구체적인 TimeStamp 주석을 사용할 수 있습니다. 먼저 코드는 Timestamp 속성을 ConcurrencyCheck 속성과 동일하게 처리하지만 code first에서 생성하는 데이터베이스 필드가 null을 허용하지 않는지 확인합니다. 지정된 클래스에는 타임스탬프 속성이 하나만 있을 수 있습니다.

블로그 클래스에 다음 속성을 추가합니다.

    [Timestamp]
    public Byte[] TimeStamp { get; set; }

Code first는 null을 허용하지 않는 타임스탬프 열을 데이터베이스 테이블에 만듭니다.

Blogs table with time stamp column

 

테이블 및 열

Code First에서 데이터베이스를 만들도록 하는 경우 데이터베이스를 만드는 테이블 및 열의 이름을 변경할 수 있습니다. 기존 데이터베이스에서 Code First를 사용할 수도 있습니다. 그러나 도메인의 클래스 및 속성 이름이 데이터베이스의 테이블 및 열 이름과 항상 일치하는 것은 아닙니다.

내 클래스는 Blog 이름이 지정되고 규칙에 따라 code first 는 Blogs 테이블에 매핑될 것이라고 가정합니다. 그렇지 않은 경우 Table 특성을 사용하여 테이블의 이름을 지정할 수 있습니다. 예를 들어 여기서 주석은 테이블 이름이 InternalBlogs임을 지정합니다.

    [Table("InternalBlogs")]
    public class Blog

Column 주석은 매핑된 열의 특성을 지정하는 데 더 능숙합니다. 이름, 데이터 형식 또는 열이 테이블에 표시되는 순서를 지정할 수 있습니다. Column 특성의 예는 다음과 같습니다.

    [Column("BlogDescription", TypeName="ntext")]
    public String Description {get;set;}

열의 TypeName 특성을 DataType DataAnnotation과 혼동하지 마세요. DataType은 UI에 사용되는 주석이며 Code First에서 무시됩니다.

다시 생성된 테이블은 다음과 같습니다. 테이블 이름이 InternalBlogs로 변경되었으며 복합 형식의 Description 열은 이제 BlogDescription입니다. 이름은 주석에 지정되었으므로 code first는 복합 형식의 이름으로 열 이름을 시작하는 규칙을 사용하지 않습니다.

Blogs table and column renamed

 

DatabaseGenerated

계산 속성을 갖는 기능은 중요한 데이터베이스 기능입니다. Code First 클래스를 계산 열이 포함된 테이블에 매핑하는 경우 Entity Framework에서 해당 열을 업데이트하려고 하지 않도록 합니다. 그러나 데이터를 삽입하거나 업데이트한 후 EF에서 해당 값을 데이터베이스에서 반환하려고 합니다. DatabaseGenerated 주석을 사용하여 Computed 열거형과 함께 클래스의 해당 속성에 플래그를 지정할 수 있습니다. 다른 열거형은 NoneIdentity입니다.

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime DateCreated { get; set; }

Code first가 데이터베이스를 생성할 때 바이트 또는 타임스탬프 열에서 생성된 데이터베이스를 사용할 수 있습니다. 그렇지 않으면 code first가 계산 열에 대한 수식을 확인할 수 없으므로 기존 데이터베이스를 가리킬 때만 사용해야 합니다.

위에서 기본적으로 정수인 키 속성은 데이터베이스의 ID 키가 됩니다. 이는 DatabaseGeneratedDatabaseGeneratedOption.Identity로 설정하는 것과 같습니다. ID 키가 되지 않도록 하기 위해 값을 DatabaseGeneratedOption.None으로 설정할 수 있습니다.

 

색인

참고 항목

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

IndexAttribute를 사용하여 하나 이상의 열에 인덱스를 만들 수 있습니다. 하나 이상의 속성에 특성을 추가하면 EF가 데이터베이스를 만들 때 데이터베이스에 해당 인덱스를 만들거나 Code First 마이그레이션 사용하는 경우 해당 CreateIndex 호출을 스캐폴드합니다.

예를 들어 다음 코드는 데이터베이스의 Posts 테이블 Rating 열 에 인덱스가 생성됩니다.

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index]
        public int Rating { get; set; }
        public int BlogId { get; set; }
    }

기본적으로 인덱스 이름은 IX_<속성 이름>(위의 예제에서는 IX_Rating)으로 지정됩니다. 또한 인덱스 이름을 지정할 수 있습니다. 다음 예제에서는 인덱스의 이름을 PostRatingIndex로 지정합니다.

    [Index("PostRatingIndex")]
    public int Rating { get; set; }

기본적으로 인덱스는 고유하지 않지만 IsUnique로 명명된 매개 변수를 사용하여 인덱스가 고유하도록 지정할 수 있습니다. 다음 예제에서는 User의 로그인 이름에 고유한 인덱스를 소개합니다.

    public class User
    {
        public int UserId { get; set; }

        [Index(IsUnique = true)]
        [StringLength(200)]
        public string Username { get; set; }

        public string DisplayName { get; set; }
    }

다중 열 인덱스

다중 열에 걸쳐 있는 인덱스는 지정된 테이블에 대해 여러 인덱스 주석에서 동일한 이름을 사용하여 지정됩니다. 다중 열 인덱스를 만들 때 인덱스의 열 순서를 지정해야 합니다. 예를 들어 다음 코드는 RatingBlogIdIX_BlogIdAndRating이라는 다중 열 인덱스를 만듭니다. BlogId는 인덱스에서 첫 번째 열이고 Rating은 두 번째 열입니다.

    public class Post
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        [Index("IX_BlogIdAndRating", 2)]
        public int Rating { get; set; }
        [Index("IX_BlogIdAndRating", 1)]
        public int BlogId { get; set; }
    }

 

관계 특성: InverseProperty 및 ForeignKey

참고 항목

이 페이지에서는 데이터 주석을 사용하여 Code First 모델에서 관계를 설정하는 방법에 대한 정보를 제공합니다. EF의 관계 및 관계를 사용하여 데이터에 액세스하고 조작하는 방법에 대한 일반적인 내용은 관계 및 탐색 속성을 참조하세요.*

Code first 규칙은 모델에서 가장 일반적인 관계를 처리하지만 도움이 필요한 경우도 있습니다.

Blog 클래스에서 키 속성의 이름을 변경하면 Post에 대한 관계에 문제가 발생했습니다. 

데이터베이스를 생성할 때 code first 는 게시물 클래스의 BlogId 속성을 보고 클래스 이름 및 Id와 일치하는 규칙에 따라 Blog 클래스의 외래 키로 인식합니다. 그러나 블로그 클래스에는 BlogId 속성이 없습니다. 이에 대한 해결 방법은 Post에서 탐색 속성을 만들고 ForeignKey DataAnnotation을 사용하여 code first가 두 클래스(Post.BlogId 속성 사용) 간의 관계를 빌드하는 방법 및 데이터베이스에서 제약 조건을 지정하는 방법을 이해하는 데 도움이 되는 것입니다.

    public class Post
    {
            public int Id { get; set; }
            public string Title { get; set; }
            public DateTime DateCreated { get; set; }
            public string Content { get; set; }
            public int BlogId { get; set; }
            [ForeignKey("BlogId")]
            public Blog Blog { get; set; }
            public ICollection<Comment> Comments { get; set; }
    }

데이터베이스의 제약 조건은 InternalBlogs.PrimaryTrackingKeyPosts.BlogId 간의 관계를 보여줍니다. 

relationship between InternalBlogs.PrimaryTrackingKey and Posts.BlogId

InverseProperty는 클래스 간에 여러 관계가 있는 경우에 사용됩니다.

Post 수업에서는 누가 블로그 게시물을 작성했는지, 누가 편집했는지 추적할 수 있습니다. 다음은 게시물 클래스에 대한 두 가지 새로운 탐색 속성입니다.

    public Person CreatedBy { get; set; }
    public Person UpdatedBy { get; set; }

또한 이러한 속성에서 참조하는 Person 클래스에 추가해야 합니다. Person 클래스에는 사용자가 작성한 모든 게시물에 대한 탐색 속성과 해당 사용자가 업데이트한 모든 게시물에 대한 탐색 속성인 Post로 돌아가는 탐색 속성이 있습니다.

    public class Person
    {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<Post> PostsWritten { get; set; }
            public List<Post> PostsUpdated { get; set; }
    }

Code first는 두 클래스의 속성을 자체적으로 일치시킬 수 없습니다. Posts에 대한 데이터베이스 테이블에는 CreatedBy 사용자용 및 UpdatedBy 사용자용 외래 키가 하나 있어야 하지만 code first는 Person_Id, Person_Id1, CreatedBy_IdUpdatedBy_Id 네 개의 외래 키 속성을 만듭니다.

Posts table with extra foreign keys

이러한 문제를 해결하려면 InverseProperty 주석을 사용하여 속성의 맞춤을 지정할 수 있습니다.

    [InverseProperty("CreatedBy")]
    public List<Post> PostsWritten { get; set; }

    [InverseProperty("UpdatedBy")]
    public List<Post> PostsUpdated { get; set; }

사용자의 PostsWritten 속성은 Post 형식을 참조한다는 것을 알고 있으므로 Post.CreatedBy에 대한 관계를 빌드합니다. 마찬가지로 PostsUpdatedPost.UpdatedBy에 연결됩니다. 그리고 code first는 추가 외래 키를 만들지 않습니다.

Posts table without extra foreign keys

 

요약

DataAnnotations를 사용하면 code first 클래스에서 클라이언트 및 서버 측 유효성 검사를 설명할 수 있을 뿐만 아니라 해당 규칙에 따라 code first가 클래스에 대해 수행할 가정을 향상시키고 수정할 수도 있습니다. DataAnnotations를 사용하면 데이터베이스 스키마 생성을 구동할 수 있을 뿐만 아니라 code first 클래스를 기존 데이터베이스에 매핑할 수도 있습니다.

매우 유연하지만 DataAnnotations는 code first 클래스에서 수행할 수 있는 가장 일반적으로 필요한 구성 변경 사항만 제공합니다. 일부 에지 사례에 대해 클래스를 구성하려면 대체 구성 메커니즘인 Code First의 흐름 API를 확인해야 합니다.