다음을 통해 공유


자습서: ASP.NET MVC 앱에 대한 보다 복잡한 데이터 모델 만들기

이전 자습서에서는 세 개의 엔터티로 구성된 간단한 데이터 모델을 사용했습니다. 이 자습서에서는 엔터티와 관계를 더 추가하고 서식, 유효성 검사 및 데이터베이스 매핑 규칙을 지정하여 데이터 모델을 사용자 지정합니다. 이 문서에서는 엔터티 클래스에 특성을 추가하고 데이터베이스 컨텍스트 클래스에 코드를 추가하여 데이터 모델을 사용자 지정하는 두 가지 방법을 보여 줍니다.

완료되면 엔터티 클래스는 다음 그림에 표시된 완성된 데이터 모델을 구성하게 됩니다.

School_class_diagram

이 자습서에서는 다음을 수행합니다.

  • 데이터 모델 사용자 지정
  • 학생 엔터티 업데이트
  • Instructor 엔터티 만들기
  • OfficeAssignment 엔터티 만들기
  • 과정 엔터티 수정
  • 부서 엔터티 만들기
  • 등록 엔터티 수정
  • 데이터베이스 컨텍스트에 코드 추가
  • 테스트 데이터로 데이터베이스 시드
  • 마이그레이션 추가
  • 데이터베이스 업데이트

필수 조건

데이터 모델 사용자 지정

이 섹션에서는 서식 지정, 유효성 검사 및 데이터베이스 매핑 규칙을 지정하는 특성을 사용하여 데이터 모델을 사용자 지정하는 방법을 배웁니다. 그런 다음 다음 몇 가지 섹션에서는 이미 만든 클래스에 특성을 추가하고 모델의 나머지 엔터티 형식에 대한 새 클래스를 만들어 전체 School 데이터 모델을 만듭니다.

DataType 특성

학생 등록 날짜의 경우, 이 필드에서 필요한 것은 날짜이지만 현재 모든 웹 페이지는 날짜와 함께 시간을 표시합니다. 데이터 주석 특성을 사용하면 데이터를 표시하는 모든 보기에서 표시 형식을 해결하는 하나의 코드 변경을 만들 수 있습니다. 수행 방법의 예제를 보려면 특성을 Student 클래스의 EnrollmentDate 속성에 추가합니다.

Models\Student.cs 다음 예제와 같이 네임스페이스에 대한 System.ComponentModel.DataAnnotations 문을 추가하고 using 속성에 EnrollmentDate 특성을 추가 DataType DisplayFormat 합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

DataType 특성은 데이터베이스 내장 형식보다 더 구체적인 데이터 형식을 지정하는 데 사용됩니다. 이 경우에는 날짜 및 시간이 아닌 날짜만 추적하고자 합니다. DataType 열거형날짜, 시간, PhoneNumber, Currency, EmailAddress 등과 같은 많은 데이터 형식에 대해 제공합니다. DataType 특성을 통해 응용 프로그램에서 자동으로 유형별 기능을 제공하도록 설정할 수도 있습니다. 예를 들어 DataType.EmailAddress에 대한 링크를 만들 수 있으며 HTML5를 지원하는 브라우저에서 DataType.Date에 대한 날짜 선택기를 제공할 수 있습니다.mailto: DataType 특성은 HTML 5 브라우저에서 이해할 수 있는 HTML 5 데이터(발음된 데이터 대시) 특성을 내보냅니다. DataType 특성은 유효성 검사를 제공하지 않습니다.

DataType.Date는 표시되는 날짜의 서식을 지정하지 않습니다. 기본적으로 데이터 필드는 서버의 CultureInfo에 따라 기본 형식에 따라 표시됩니다.

DisplayFormat 특성은 날짜 형식을 명시적으로 지정하는 데 사용됩니다.

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

이 설정은 ApplyFormatInEditMode 편집을 위해 텍스트 상자에 값이 표시될 때 지정된 서식도 적용되도록 지정합니다. (예를 들어 통화 값과 같은 일부 필드에는 텍스트 상자의 통화 기호를 편집하지 않을 수 있습니다.)

DisplayFormat 특성 자체를 사용할 수 있지만 일반적으로 DataType 특성도 사용하는 것이 좋습니다. 이 특성은 DataType 화면에서 렌더링하는 방법과 달리 데이터의 의미 체계를 전달하며, 사용하지 않는 DisplayFormat다음과 같은 이점을 제공합니다.

  • 브라우저는 HTML5 기능을 활성화할 수 있습니다(예: 달력 컨트롤, 로캘에 적합한 통화 기호, 이메일 링크, 일부 클라이언트 쪽 입력 유효성 검사 등을 표시하기 위해).
  • 기본적으로 브라우저는 로캘에 따라 올바른 형식을 사용하여 데이터를 렌더링합니다.
  • DataType 특성을 사용하면 MVC가 데이터를 렌더링할 올바른 필드 템플릿을 선택할 수 있습니다(DisplayFormat문자열 템플릿을 사용). 자세한 내용은 Brad Wilson의 ASP.NET MVC 2 템플릿을 참조하세요. (MVC 2용으로 작성되었지만 이 문서는 여전히 현재 버전의 ASP.NET MVC에 적용됩니다.)

날짜 필드와 함께 특성을 사용하는 DataType 경우 Chrome 브라우저에서 필드가 올바르게 렌더링되도록 특성도 지정 DisplayFormat 해야 합니다. 자세한 내용은 이 StackOverflow 스레드를 참조 하세요.

MVC에서 다른 날짜 형식을 처리하는 방법에 대한 자세한 내용은 MVC 5 소개: 편집 방법 및 편집 보기 검사로 이동하여 페이지에서 "국제화"를 검색합니다.

학생 인덱스 페이지를 다시 실행하고 등록 날짜에 대한 시간이 더 이상 표시되지 않습니다. 모델을 사용하는 Student 모든 보기도 마찬가지입니다.

Students_index_page_with_formatted_date

The StringLengthAttribute

특성을 사용하여 데이터 유효성 검사 규칙 및 유효성 검사 오류 메시지를 지정할 수 있습니다. StringLength 특성데이터베이스의 최대 길이를 설정하고 ASP.NET MVC에 대한 클라이언트 쪽 및 서버 쪽 유효성 검사를 제공합니다. 이 특성의 최소 문자열 길이를 지정할 수도 있지만, 최소값은 데이터베이스 스키마에 영향을 주지 않습니다.

사용자가 이름을 50자 이하로 입력하였는지를 확인한다고 가정합니다. 이 제한을 추가하려면 다음 예제와 같이 StringLength 특성을 LastNameFirstMidName 속성에 추가합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

StringLength 특성은 사용자가 이름의 공백을 입력하는 것을 방지하지 않습니다. RegularExpression 특성을 사용하여 입력에 제한을 적용할 수 있습니다. 예를 들어 다음 코드에서는 첫 번째 문자가 대문자여야 하고 나머지 문자는 사전순이어야 합니다.

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

MaxLength 특성은 StringLength 특성과 유사한 기능을 제공하지만 클라이언트 쪽 유효성 검사를 제공하지는 않습니다.

애플리케이션을 실행하고 학생 탭을 클릭합니다. 다음과 같은 오류가 발생합니다.

데이터베이스를 만든 이후 'SchoolContext' 컨텍스트를 지원하는 모델이 변경되었습니다. Code First 마이그레이션 사용하여 데이터베이스(https://go.microsoft.com/fwlink/?LinkId=238269)를 업데이트하는 것이 좋습니다.

데이터베이스 모델이 데이터베이스 스키마를 변경해야 하는 방식으로 변경되었으며 Entity Framework에서 이를 감지했습니다. UI를 사용하여 데이터베이스에 추가한 데이터를 잃지 않고 마이그레이션을 사용하여 스키마를 업데이트합니다. 메서드에서 Seed 만든 데이터를 변경한 경우 메서드에서 사용 Seed 중인 AddOrUpdate 메서드로 인해 원래 상태로 다시 변경됩니다. AddOrUpdate는 데이터베이스 용어의 "upsert" 작업과 동일합니다.

PMC(패키지 관리자 콘솔)에서 다음 명령을 입력합니다.

add-migration MaxLengthOnNames
update-database

add-migration 명령은 timeStamp>_MaxLengthOnNames.cs 파일을 <만듭니다. 이 파일에는 현재 데이터 모델과 일치하도록 데이터베이스를 업데이트하는 Up 메서드의 코드가 포함됩니다. update-database 명령은 해당 코드를 실행합니다.

마이그레이션 파일 이름 앞에 추가된 타임스탬프는 Entity Framework에서 마이그레이션 순서를 지정하는 데 사용됩니다. 명령을 실행하기 전에 여러 마이그레이션을 update-database 만든 다음 모든 마이그레이션이 생성된 순서대로 적용됩니다.

만들기 페이지를 실행하고 50자보다 긴 이름을 입력합니다. 만들기를 클릭하면 클라이언트 쪽 유효성 검사에서 오류 메시지가 표시됩니다. LastName 필드는 최대 길이가 50인 문자열이어야 합니다.

열 특성

또한 특성을 사용하여 클래스 및 속성을 데이터베이스에 매핑하는 방법을 제어할 수 있습니다. 필드에 중간 이름이 포함될 수 있으므로 첫 번째 이름 필드에 이름 FirstMidName을 사용한 경우를 가정합니다. 하지만 데이터베이스에 임시 쿼리를 작성하는 사용자는 해당 이름이 익숙하기 때문에 데이터베이스 열 이름을 FirstName으로 하려고 합니다. 이 매핑을 수행하기 위해 Column 특성을 사용할 수 있습니다.

Column 특성은 데이터베이스를 만드는 시기를 지정하고 FirstMidName 속성에 매핑하는 Student 테이블의 열 이름은 FirstName이 됩니다. 즉, 코드가 Student.FirstMidName을 참조하는 경우 데이터는 Student 테이블의 FirstName 열에서 가져오거나 업데이트됩니다. 열 이름을 지정하지 않으면 속성 이름과 동일한 이름이 지정됩니다.

Student.cs 파일에서 System.ComponentModel.DataAnnotations.Schema에 대한 using문을 추가하고, 다음 강조 표시된 코드에 표시된 것과 같이 FirstMidName 속성에 열 이름 특성을 추가합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]       
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Column 특성을 추가하면 SchoolContext를 지원하는 모델이 변경되므로 데이터베이스와 일치하지 않습니다. PMC에 다음 명령을 입력하여 다른 마이그레이션을 만듭니다.

add-migration ColumnFirstName
update-database

서버 탐색기에서 Student 테이블을 두 번 클릭하여 Student 테이블 디자이너를 엽니다.

다음 이미지는 처음 두 마이그레이션을 적용하기 전의 원래 열 이름을 보여 줍니다. 열 이름이 변경 FirstMidName FirstName되는 것 외에도 두 개의 이름 열이 길이에서 MAX 50자로 변경되었습니다.

두 학생 테이블의 이름 및 데이터 형식의 차이점을 보여 주는 두 개의 스크린샷

이 자습서의 뒷부분에서 볼 수 있듯이 Fluent API를 사용하여 데이터베이스 매핑을 변경할 수도 있습니다.

참고 항목

다음 섹션의 모든 엔터티 클래스 만들기를 완료하기 전에 컴파일을 시도하는 경우 컴파일러 오류가 발생할 수 있습니다.

학생 엔터티 업데이트

Models\Student.cs 앞에서 추가한 코드를 다음 코드로 바꿉다. 변경 내용은 강조 표시되어 있습니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }

        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

필수 특성

필수 특성이름 속성을 필수 필드로 만듭니다. Required attribute DateTime, int, double 및 float와 같은 값 형식에는 필요하지 않습니다. 값 형식은 null 값을 할당할 수 없으므로 기본적으로 필수 필드로 처리됩니다.

MinimumLength가 적용되려면 Required 특성이 MinimumLength와 함께 사용되어야 합니다.

[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLengthRequired는 유효성 검사 충족에 공백을 허용합니다. 문자열을 RegularExpression 완전히 제어하기 위해 특성을 사용합니다.

표시 특성

Display 특성은 텍스트 상자에 대한 캡션이 각 인스턴스의 속성 이름 대신 “First Name”, “Last Name”, “Full Name” 및 “Enrollment Date”여야 함을 지정합니다(단어를 분할하는 공백 없음).

FullName 계산 속성

FullName은 다른 두 개의 속성을 연결하여 생성되는 값을 반환하는 계산된 속성입니다. 따라서 접근자 get 만 있고 데이터베이스에 열이 생성되지 않습니다 FullName .

Instructor 엔터티 만들기

템플릿 코드를 다음 코드로 바꿔서 Models\Instructor.cs 만듭니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor
    {
        public int ID { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstMidName { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get { return LastName + ", " + FirstMidName; }
        }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

StudentInstructor 엔터티의 여러 속성은 동일합니다. 이 시리즈의 뒷부분에 나오는 상속 구현 자습서에서는 이 코드를 리팩터링하여 중복을 제거합니다.

한 줄에 여러 특성을 배치할 수 있으므로 다음과 같이 강사 클래스를 작성할 수도 있습니다.

public class Instructor
{
   public int ID { get; set; }

   [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
   public string LastName { get; set; }

   [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
   public string FirstMidName { get; set; }

   [DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
   public DateTime HireDate { get; set; }

   [Display(Name = "Full Name")]
   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

   public virtual ICollection<Course> Courses { get; set; }
   public virtual OfficeAssignment OfficeAssignment { get; set; }
}

과정 및 OfficeAssignment 탐색 속성

CoursesOfficeAssignment 속성은 탐색 속성입니다. 앞에서 설명한 대로 지연 로드라는 Entity Framework 기능을 활용할 수 있도록 일반적으로 가상으로 정의됩니다. 또한 탐색 속성이 여러 엔터티를 보유할 수 있는 경우 해당 형식은 ICollection<T> 인터페이스를 구현해야 합니다. 예를 들어 IList<T>는 Add를 구현하지 않으므로 IEnumerable T> IEnumerable<T> 를 사용할 수<없습니다.

강사는 다양한 과정을 가르칠 수 있으므로 Courses 엔터티 컬렉션 Course 으로 정의됩니다.

public virtual ICollection<Course> Courses { get; set; }

비즈니스 규칙에 따르면 강사는 최대 하나의 사무실만 가질 수 있으므로 OfficeAssignment 단일 OfficeAssignment 엔터티로 정의됩니다(사무실이 할당되지 null 않은 경우일 수 있음).

public virtual OfficeAssignment OfficeAssignment { get; set; }

OfficeAssignment 엔터티 만들기

다음 코드를 사용하여 Models\OfficeAssignment.cs 만듭니다.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        [ForeignKey("Instructor")]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public virtual Instructor Instructor { get; set; }
    }
}

프로젝트를 빌드하여 변경 내용을 저장하고 컴파일러가 catch할 수 있는 복사 및 붙여넣기 오류를 만들지 않았는지 확인합니다.

키 특성

InstructorOfficeAssignment 엔터티 간에는 일 대 영 또는 일 대 일 관계가 있습니다. 사무실 할당은 할당된 강사에 관하여 존재하며, 따라서 해당 기본 키도 Instructor 엔터티에 대한 해당 외래 키입니다. 그러나 Entity Framework는 이름이 또는 클래스 ID 이름 명명 규칙을 따르지 ID 않으므로 이 엔터티의 기본 키로 자동으로 인식 InstructorID 할 수 없습니다. 따라서 Key 특성은 키로 식별하는 데 사용됩니다.

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

엔터티에 자체 기본 키가 있지만 속성의 Key 이름을 다른 classnameID 이름으로 지정하려는 경우 특성을 사용할 수도 있습니다 ID. 기본적으로 EF는 열이 식별 관계용이므로 키가 데이터베이스가 아닌 것으로 처리합니다.

ForeignKey 특성

일대일 관계 또는 두 엔터티 간의 일대일 관계(예: 사이 OfficeAssignment ) Instructor가 있는 경우 EF는 관계의 어느 쪽이 보안 주체이고 어떤 끝이 종속되어 있는지 확인할 수 없습니다. 일대일 관계에는 각 클래스에서 다른 클래스에 대한 참조 탐색 속성이 있습니다. ForeignKey 특성을 종속 클래스에 적용하여 관계를 설정할 수 있습니다. ForeignKey 특성을 생략하면 마이그레이션을 만들려고 할 때 다음 오류가 발생합니다.

'ContosoUniversity.Models.OfficeAssignment' 및 'ContosoUniversity.Models.Instructor' 형식 간의 연결의 주체 끝을 확인할 수 없습니다. 관계 흐름 API 또는 데이터 주석을 사용하여 이 연결의 주체 끝을 명시적으로 구성해야 합니다.

자습서의 뒷부분에서 흐름 API와 이 관계를 구성하는 방법을 알아보세요.

강사 탐색 속성

Instructor 엔터티에는 nullable OfficeAssignment 탐색 속성(강사가 사무실 할당이 없을 수 있으므로)이 있고 OfficeAssignment 엔터티에는 null을 허용하지 않는 Instructor 탐색 속성이 있습니다(강사 InstructorID 없이는 사무실 할당이 존재할 수 없으므로 null을 허용하지 않음). 엔터티에 Instructor 관련 OfficeAssignment 엔터티가 있는 경우 각 엔터티는 탐색 속성에 있는 다른 엔터티에 대한 참조를 갖게 됩니다.

강사 탐색 속성에 특성을 배치 [Required] 하여 관련 강사가 있어야 한다고 지정할 수 있지만, 이 테이블의 키이기도 한 InstructorID 외래 키는 null을 허용하지 않으므로 그렇게 할 필요가 없습니다.

과정 엔터티 수정

Models\Course.cs 앞에서 추가한 코드를 다음 코드로 바꿉다.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
   public class Course
   {
      [DatabaseGenerated(DatabaseGeneratedOption.None)]
      [Display(Name = "Number")]
      public int CourseID { get; set; }

      [StringLength(50, MinimumLength = 3)]
      public string Title { get; set; }

      [Range(0, 5)]
      public int Credits { get; set; }

      public int DepartmentID { get; set; }

      public virtual Department Department { get; set; }
      public virtual ICollection<Enrollment> Enrollments { get; set; }
      public virtual ICollection<Instructor> Instructors { get; set; }
   }
}

과정 엔터티에는 관련 Department 엔터티를 가리키는 외래 키 속성 DepartmentID 이 있으며 탐색 속성이 있습니다Department. Entity Framework는 관련된 엔터티에 대한 탐색 속성이 있는 경우 사용자가 외래 키 속성을 데이터 모델에 추가하지 않아도 됩니다. EF는 필요한 위치에 데이터베이스에 외장 키를 자동으로 만듭니다. 하지만 데이터 모델에 외래 키가 있으면 더 간단하고 더 효율적으로 업데이트를 수행할 수 있습니다. 예를 들어 편집 Department 할 과정 엔터티를 가져올 때 엔터티를 로드하지 않으면 null이므로 과정 엔터티를 업데이트할 때 먼저 엔터티를 가져와 Department 야 합니다. 외래 키 속성 DepartmentID가 데이터 모델에 포함된 경우 업데이트하기 전에 Department 엔터티를 페치할 필요가 없습니다.

DatabaseGenerated 특성

속성에 CourseID None 매개 변수가 있는 DatabaseGenerated 특성데이터베이스에서 생성되지 않고 사용자가 기본 키 값을 제공하도록 지정합니다.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

기본적으로 Entity Framework는 기본 키 값이 데이터베이스에 의해 생성된다고 가정합니다. 이는 대부분의 시나리오에서 사용자가 원하는 것입니다. 그러나 Course 엔터티의 경우 한 부서에 지정한 1,000개 시리즈, 다른 부서에 지정한 2,000개 시리즈와 같은 사용자 지정 강좌 수를 사용하게 됩니다.

외래 키 및 탐색 속성

Course 엔터티의 외래 키 속성 및 탐색 속성은 다음 관계를 반영합니다.

  • 강좌는 한 부서에 할당되므로, 위에서 언급한 이유로 DepartmentID 외래 키와 Department 탐색 속성이 있습니다.

    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
    
  • 강좌에는 등록된 학생이 여러 명 있을 수 있으므로 Enrollments 탐색 속성은 컬렉션입니다.

    public virtual ICollection<Enrollment> Enrollments { get; set; }
    
  • 여러 강사가 한 강좌를 수업할 수 있으므로 Instructors 탐색 속성은 컬렉션입니다.

    public virtual ICollection<Instructor> Instructors { get; set; }
    

부서 엔터티 만들기

다음 코드를 사용하여 Models\Department.cs 만듭니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
   public class Department
   {
      public int DepartmentID { get; set; }

      [StringLength(50, MinimumLength=3)]
      public string Name { get; set; }

      [DataType(DataType.Currency)]
      [Column(TypeName = "money")]
      public decimal Budget { get; set; }

      [DataType(DataType.Date)]
      [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
      [Display(Name = "Start Date")]
      public DateTime StartDate { get; set; }

      public int? InstructorID { get; set; }

      public virtual Instructor Administrator { get; set; }
      public virtual ICollection<Course> Courses { get; set; }
   }
}

열 특성

이전에 Column 특성을 사용하여 열 이름 매핑을 변경했습니다. 엔터티에 대한 Department 코드에서 이 특성은 데이터베이스의 Column SQL Server money 형식을 사용하여 열이 정의되도록 SQL 데이터 형식 매핑을 변경하는 데 사용됩니다.

[Column(TypeName="money")]
public decimal Budget { get; set; }

Entity Framework는 일반적으로 속성에 대해 정의한 CLR 형식에 따라 적절한 SQL Server 데이터 형식을 선택하므로 열 매핑은 일반적으로 필요하지 않습니다. CLR decimal 형식은 SQL Server decimal 유형에 매핑됩니다. 그러나 이 경우 열이 통화 금액을 보유하게 되며 통화 데이터 형식이 더 적합하다는 것을 알고 있습니다. CLR 데이터 형식 및 SQL Server 데이터 형식과 일치하는 방법에 대한 자세한 내용은 Entity FrameworkTypes용 SqlClient를 참조 하세요.

외래 키 및 탐색 속성

외래 키 및 탐색 속성은 다음과 같은 관계를 반영합니다.

  • 부서는 관리자가 있을 수도 있고 없을 수도 있으며, 관리자는 항상 강사입니다. 따라서 InstructorID 속성은 엔터티의 외래 키 Instructor 로 포함되며 형식 지정 후에 int 물음표가 추가되어 속성을 nullable로 표시합니다. 탐색 속성의 이름은 지정 Administrator 되지만 엔터티를 Instructor 보유합니다.

    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
    
  • 부서에는 많은 과정이 있을 수 있으므로 탐색 속성이 있습니다 Courses .

    public virtual ICollection<Course> Courses { get; set; }
    

    참고 항목

    규칙에 따라 Entity Framework는 null 비허용 외래 키 및 다대다 관계에 대한 하위 삭제를 사용하도록 허용합니다. 이는 마이그레이션을 추가하려고 하면 예외가 발생하는 순환 하위 삭제 규칙으로 이어질 수 있습니다. 예를 들어 속성을 nullable로 정의 Department.InstructorID 하지 않은 경우 다음과 같은 예외 메시지가 표시됩니다. "참조 관계로 인해 허용되지 않는 순환 참조가 발생합니다." 비즈니스 규칙에 따라 null을 허용하지 않는 속성이 필요한 InstructorID 경우 다음 흐름 API 문을 사용하여 관계에 대한 연계 삭제를 사용하지 않도록 설정해야 합니다.

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

등록 엔터티 수정

Models\Enrollment.cs 앞에서 추가한 코드를 다음 코드로 바꿉

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public virtual Course Course { get; set; }
        public virtual Student Student { get; set; }
    }
}

외래 키 및 탐색 속성

외래 키 속성 및 탐색 속성은 다음 관계를 반영합니다.

  • 등록 레코드는 단일 강좌에 해당하므로 CourseID 외래 키 속성 및 Course 탐색 속성이 있습니다.

    public int CourseID { get; set; }
    public virtual Course Course { get; set; }
    
  • 등록 레코드는 단일 학생에 해당하므로 StudentID 외래 키 속성 및 Student 탐색 속성이 있습니다.

    public int StudentID { get; set; }
    public virtual Student Student { get; set; }
    

다대다 관계

StudentCourse 엔터티 간에는 다 대 다 관계가 있으며, Enrollment 엔터티는 데이터베이스에서 ‘페이로드가 있는’ 다 대 다 조인 테이블로 작동합니다.Enrollment , 테이블에 조인된 테이블(이 경우 기본 키 및 Grade 속성)에 대한 외래 키 외에 추가 데이터가 포함됩니다.

다음 그림은 이러한 관계 모양을 엔터티 다이어그램으로 보여 줍니다. (이 다이어그램은 다음을 사용하여 생성되었습니다.Entity Framework Power Tools; 다이어그램 만들기는 자습서의 일부가 아니며 여기서 그림으로만 사용됩니다.)

Student-Course_many-to-many_relationship

각 관계 줄에는 한쪽 끝에 1, 다른 한쪽 끝에는 별표(*)가 있으며, 이는 일대다 관계를 나타냅니다.

Enrollment 테이블에 등급 정보가 포함되지 않은 경우 두 개의 외래 키(CourseIDStudentID)를 포함해야 합니다. 이 경우 데이터베이스에 페이로드(또는 순수 조인 테이블)가 없는 다대다 조인 테이블에 해당하며, 모델 클래스를 만들 필요가 없습니다. Instructor 엔터티와 Course 엔터티는 다대다 관계를 가지며, 여기에서 볼 수 있듯이 엔터티 클래스는 없습니다.

Instructor-Course_many-to-many_relationship

그러나 다음 데이터베이스 다이어그램과 같이 데이터베이스에는 조인 테이블이 필요합니다.

Instructor-Course_many-to-many_relationship_tables

Entity Framework는 자동으로 테이블을 만들고 CourseInstructor , 테이블과 탐색 속성을 읽고 업데이트하여 간접적으로 읽고 업데이트 Instructor.Courses Course.Instructors 합니다.

엔터티 관계 다이어그램

다음 그림에는 Entity Framework 파워 도구가 완벽한 School 모델을 만드는 다이어그램이 나와 있습니다.

School_data_model_diagram

다대다 관계선(*~*) 및 일대다 관계선(1~*) 외에도, 강사와 OfficeAssignment 부서 엔터티 간의 Instructor 일대다 관계선(1~0..1)과 0.1~0.1 사이의 일대다 관계선(0..1에서 *)을 볼 수 있습니다.

데이터베이스 컨텍스트에 코드 추가

다음으로 클래스에 새 엔터티를 SchoolContext 추가하고 Fluent API 호출을 사용하여 일부 매핑을 사용자 지정합니다. API는 다음 예제와 같이 일련의 메서드 호출을 단일 문으로 문자열링하는 데 자주 사용되기 때문에 "fluent"입니다.

modelBuilder.Entity<Course>()
     .HasMany(c => c.Instructors).WithMany(i => i.Courses)
     .Map(t => t.MapLeftKey("CourseID")
         .MapRightKey("InstructorID")
         .ToTable("CourseInstructor"));

이 자습서에서는 특성으로는 수행할 수 없는 데이터베이스 매핑에만 Fluent API를 사용합니다. 그러나 대부분의 서식 지정, 유효성 검사 및 매핑 규칙을 지정하는 데 흐름 API를 사용할 수 있습니다. MinimumLength와 같은 일부 특성은 흐름 API를 통해 적용할 수 없습니다. 앞에서 MinimumLength 설명한 것처럼 스키마를 변경하지 않고 클라이언트 및 서버 쪽 유효성 검사 규칙만 적용합니다.

일부 개발자는 흐름 API를 단독으로 사용하는 것을 선호하므로 자신의 엔터티 클래스를 “정리”할 수 있습니다. 원하는 경우 특성과 흐름 API를 섞을 수 있으며, 흐름 API를 사용해야만 수행할 수 있는 몇 가지 사용자 지정이 있습니다. 하지만 일반적으로 이러한 두 가지 방법 중 하나를 선택하여 가능한 한 일관되게 사용하는 것이 좋습니다.

데이터 모델에 새 엔터티를 추가하고 특성을 사용하여 수행하지 않은 데이터베이스 매핑을 수행하려면 DAL\SchoolContext.cs 코드를 다음 코드로 바꿉다.

using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.DAL
{
   public class SchoolContext : DbContext
   {
      public DbSet<Course> Courses { get; set; }
      public DbSet<Department> Departments { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<Student> Students { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

         modelBuilder.Entity<Course>()
             .HasMany(c => c.Instructors).WithMany(i => i.Courses)
             .Map(t => t.MapLeftKey("CourseID")
                 .MapRightKey("InstructorID")
                 .ToTable("CourseInstructor"));
      }
   }
}

OnModelCreating 메서드의 새 문은 다대다 조인 테이블을 구성합니다.

  • 엔터티와 Course 엔터티 간의 Instructor 다 대 다 관계의 경우 코드는 조인 테이블의 테이블 및 열 이름을 지정합니다. Code First는 이 코드 없이 다대다 관계를 구성할 수 있지만 호출하지 않으면 열과 같은 InstructorInstructorID InstructorID 기본 이름이 표시됩니다.

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));
    

다음 코드는 특성 대신 흐름 API를 사용하여 엔터티 간의 Instructor OfficeAssignment 관계를 지정하는 방법의 예를 제공합니다.

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

"Fluent API" 문이 백그라운드에서 수행하는 작업에 대한 자세한 내용은 Fluent API 블로그 게시물을 참조하세요.

테스트 데이터로 데이터베이스 시드

만든 새 엔터티에 대한 시드 데이터를 제공하기 위해 Migrations\Configuration.cs 파일의 코드를 다음 코드로 바꿉니다.

namespace ContosoUniversity.Migrations
{
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander", 
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",    
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",     
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas", 
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",        
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",   
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",    
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",  
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var instructors = new List<Instructor>
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie", 
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",    
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",       
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",      
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",      
                    HireDate = DateTime.Parse("2004-02-12") }
            };
            instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var departments = new List<Department>
            {
                new Department { Name = "English",     Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };
            departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
            context.SaveChanges();

            var courses = new List<Course>
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
            };
            courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
            context.SaveChanges();

            var officeAssignments = new List<OfficeAssignment>
            {
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID, 
                    Location = "Thompson 304" },
            };
            officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
            context.SaveChanges();

            AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
            AddOrUpdateInstructor(context, "Chemistry", "Harui");
            AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
            AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");

            AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
            AddOrUpdateInstructor(context, "Trigonometry", "Harui");
            AddOrUpdateInstructor(context, "Composition", "Abercrombie");
            AddOrUpdateInstructor(context, "Literature", "Abercrombie");

            context.SaveChanges();

            var enrollments = new List<Enrollment>
            {
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                         s.Student.ID == e.StudentID &&
                         s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }

        void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
        {
            var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
            var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
            if (inst == null)
                crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
        }
    }
}

첫 번째 자습서에서 본 것처럼 이 코드의 대부분은 단순히 새 엔터티 개체를 업데이트하거나 만들고 테스트에 필요한 대로 샘플 데이터를 속성에 로드합니다. 그러나 엔터티와 다대다 관계가 있는 엔터티가 Instructor 어떻게 Course 처리되는지 확인합니다.

var courses = new List<Course>
{
    new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
      DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
      Instructors = new List<Instructor>() 
    },
    ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

개체를 Course 만들 때 코드를 Instructors = new List<Instructor>()사용하여 탐색 속성을 빈 컬렉션으로 초기화 Instructors 합니다. 이렇게 하면 메서드를 사용하여 이와 Course 관련된 엔터티를 Instructors.Add 추가할 Instructor 수 있습니다. 빈 목록을 만들지 않은 경우 속성이 null이고 메서드가 없으므로 이러한 관계를 Instructors 추가할 수 없습니다 Add . 또한 생성자에 목록 초기화를 추가할 수도 있습니다.

마이그레이션 추가

PMC에서 명령을 입력합니다 add-migration (아직 명령을 수행 update-database 하지 않음).

add-Migration ComplexDataModel

이 시점에서 update-database 명령 실행을 시도하면(아직 수행하지 말 것) 다음 오류가 발생합니다.

ALTER TABLE 문이 FOREIGN KEY 제약 조건 “FK_dbo.Course_dbo.Department_DepartmentID”와 충돌했습니다. 데이터베이스 “ContosoUniversity”, 테이블 “dbo.Department”, 열 ‘DepartmentID’에서 충돌이 발생합니다.

기존 데이터로 마이그레이션을 실행할 때 외래 키 제약 조건을 충족하기 위해 데이터베이스에 스텁 데이터를 삽입해야 하는 경우가 있습니다. 이것이 바로 지금 수행해야 하는 작업입니다. ComplexDataModel Up 메서드에서 생성된 코드는 Null을 허용하지 않는 DepartmentID 외래 키를 Course 테이블에 추가합니다. 코드가 실행 AddColumn 될 때 테이블에 이미 행 Course 이 있기 때문에 SQL Server가 열에 null이 될 수 없는 값을 모르기 때문에 작업이 실패합니다. 따라서 코드를 변경하여 새 열에 기본값을 부여하고 기본 부서 역할을 하는 "Temp"라는 스텁 부서를 만들어야 합니다. 따라서 메서드가 실행된 후 기존 Course 행은 모두 "Temp" 부서와 Up 관련됩니다. 메서드의 올바른 부서와 연결할 수 있습니다 Seed .

<타임스탬프>_ComplexDataModel.cs 파일을 편집하고, DepartmentID 열을 Course 테이블에 추가하는 코드 줄을 주석 처리하고, 강조 표시된 다음 코드를 추가합니다(주석 처리된 줄도 강조 표시됨).

CreateTable(
        "dbo.CourseInstructor",
        c => new
            {
                CourseID = c.Int(nullable: false),
                InstructorID = c.Int(nullable: false),
            })
        .PrimaryKey(t => new { t.CourseID, t.InstructorID })
        .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
        .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
        .Index(t => t.CourseID)
        .Index(t => t.InstructorID);

    // Create  a department for course to point to.
    Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    //  default value for FK points to department created above.
    AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); 
    //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));

    AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

메서드가 Seed 실행되면 테이블에 행을 Department 삽입하고 기존 Course 행을 해당 새 Department 행과 연결합니다. UI에 강좌를 추가하지 않은 경우 더 이상 열에 "Temp" 부서 또는 기본값이 Course.DepartmentID 필요하지 않습니다. 다른 사용자가 애플리케이션을 사용하여 강좌를 추가했을 가능성을 허용하려면 열에서 기본값을 제거하고 "Temp" 부서를 삭제하기 전에 모든 Course 행(이전 메서드 Seed 실행에서 삽입한 행뿐만 아니라)이 유효한 DepartmentID 값을 갖도록 메서드 코드를 업데이트 Seed 하려고 합니다.

데이터베이스 업데이트

타임스탬프_ComplexDataModel.cs 파일 편집<을 완료한 후 PMC에 명령을 입력 update-database 하여 마이그레이션을 실행합니다.>

update-database

참고 항목

데이터를 마이그레이션하고 스키마를 변경할 때 다른 오류가 발생할 수 있습니다. 해결할 수 없는 마이그레이션 오류가 발생하면 연결 문자열에서 데이터베이스 이름을 변경하거나 데이터베이스를 삭제할 수 있습니다. 가장 간단한 방법은 Web.config 파일에서 데이터베이스 이름을 바꾸는 것입니다. 다음 예제에서는 이름이 CU_Test 변경된 것을 보여줍니다.

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;" 
      providerName="System.Data.SqlClient" />

새 데이터베이스를 사용하면 마이그레이션할 데이터가 없으며 명령이 update-database 오류 없이 완료할 가능성이 훨씬 높습니다. 데이터베이스를 삭제하는 방법에 대한 지침은 Visual Studio 2012에서 데이터베이스를 삭제하는 방법을 참조하세요.

실패하면 PMC에 다음 명령을 입력하여 데이터베이스를 다시 초기화할 수 있습니다.

update-database -TargetMigration:0

이전과 마찬가지로 서버 탐색기에서 데이터베이스를 열고 테이블 노드를 확장하여 모든 테이블이 만들어졌는지 확인합니다. (계속 있는 경우이전 시간부터 서버 탐색기를 열고 새로 고침 단추를 클릭합니다.)

서버 탐색기 창을 보여 주는 스크린샷. 학교 컨텍스트 아래의 Tables 폴더가 열려 있습니다.

테이블에 대한 CourseInstructor 모델 클래스를 만들지 않았습니다. 앞에서 설명한 대로 이 테이블은 엔터티와 Course 엔터티 간의 Instructor 다대다 관계에 대한 조인 테이블입니다.

테이블을 마우스 오른쪽 단추로 클릭하고 CourseInstructor 테이블 데이터 표시를 선택하여 탐색 속성에 추가 Course.Instructors 한 엔터티의 Instructor 결과로 테이블 데이터에 데이터가 있는지 확인합니다.

Table_data_in_CourseInstructor_table

코드 가져오기

완료된 프로젝트 다운로드

추가 리소스

다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 - 권장 리소스에서 찾을 수 있습니다.

다음 단계

이 자습서에서는 다음을 수행합니다.

  • 데이터 모델 사용자 지정
  • 업데이트된 학생 엔터티
  • Instructor 엔터티 만들기
  • OfficeAssignment 엔터티 만들기
  • 강좌 엔터티 수정
  • 부서 엔터티를 만들었습니다.
  • 등록 엔터티 수정
  • 데이터베이스 컨텍스트에 코드 추가
  • 테스트 데이터로 데이터베이스 시드
  • 마이그레이션 추가
  • 데이터베이스 업데이트

다음 문서로 이동하여 Entity Framework가 탐색 속성에 로드하는 관련 데이터를 읽고 표시하는 방법을 알아봅니다.