다음을 통해 공유


ASP.NET MVC 애플리케이션에 대한 더 복잡한 데이터 모델 만들기(10개 중 4개)

작성자: Tom Dykstra

Contoso University 샘플 웹 애플리케이션은 Entity Framework 5 Code First 및 Visual Studio 2012를 사용하여 ASP.NET MVC 4 애플리케이션을 만드는 방법을 보여 줍니다. 자습서 시리즈에 대한 정보는 시리즈의 첫 번째 자습서를 참조하세요.

참고

resolve 수 없는 문제가 발생하면 완료된 장을 다운로드하고 문제를 재현해 보세요. 일반적으로 코드를 완료된 코드와 비교하여 문제에 대한 솔루션을 찾을 수 있습니다. 몇 가지 일반적인 오류 및 해결 방법은 오류 및 해결 방법을 참조하세요.

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

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

School_class_diagram

특성을 사용하여 데이터 모델 사용자 지정

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

DataType 특성

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

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

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int StudentID { 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 열거형Date, Time, PhoneNumber, Currency, EmailAddress 등과 같은 많은 데이터 형식을 제공합니다. DataType 특성을 통해 응용 프로그램에서 자동으로 형식별 기능을 제공하도록 설정할 수도 있습니다. 예를 들어 mailto:DataType.EmailAddress에 대한 링크를 만들 수 있으며 HTML5를 지원하는 브라우저에서 DataType.Date에 대한 날짜 선택기를 제공할 수 있습니다. DataType 특성은 HTML 5 브라우저에서 이해할 수 있는 HTML 5 데이터-(발음된 데이터 대시) 특성을 내보냅니다. DataType 특성은 유효성 검사를 제공하지 않습니다.

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

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

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

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

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

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

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

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

Students_index_page_with_formatted_date

The StringLengthAttribute

특성을 사용하여 데이터 유효성 검사 규칙 및 메시지를 지정할 수도 있습니다. 사용자가 이름을 50자 이하로 입력하였는지를 확인한다고 가정합니다. 이 제한을 추가하려면 다음 예제와 같이 및 FirstMidName 속성에 LastNameStringLength 특성을 추가합니다.

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int StudentID { 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 MaxLengthOnNamestimeStamp>_MaxLengthOnNames.cs라는< 파일을 만듭니다. 이 파일에는 현재 데이터 모델과 일치하도록 데이터베이스를 업데이트하는 코드가 포함되어 있습니다. 마이그레이션 파일 이름 앞에 추가된 타임스탬프는 Entity Framework에서 마이그레이션 순서를 지정하는 데 사용됩니다. 여러 마이그레이션을 만든 후 데이터베이스를 삭제하거나 마이그레이션을 사용하여 프로젝트를 배포하는 경우 모든 마이그레이션이 생성된 순서대로 적용됩니다.

만들기 페이지를 실행하고 50자보다 긴 이름을 입력합니다. 50자를 초과하는 즉시 클라이언트 쪽 유효성 검사에 오류 메시지가 즉시 표시됩니다.

클라이언트 쪽 val 오류

열 특성

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

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

다음 강조 표시된 코드와 같이 System.ComponentModel.DataAnnotations.Schema 및 열 이름 특성에 FirstMidName 대한 using 문을 속성에 추가합니다.

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

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int StudentID { 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

서버 Explorer(웹용 Express를 사용하는 경우 데이터베이스 Explorer)에서 Student 테이블을 두 번 클릭합니다.

서버 Explorer Student 테이블을 보여 주는 스크린샷

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

서버 Explorer Student 테이블을 보여 주는 스크린샷 이전 스크린샷의 이름 줄이 첫 번째 중간 이름으로 읽도록 변경되었습니다.

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

참고

이러한 엔터티 클래스를 모두 만들기 전에 컴파일하려고 하면 컴파일러 오류가 발생할 수 있습니다.

강사 엔터티 만들기

Instructor_entity

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 InstructorID { 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; }

        public string FullName
        {
            get { return LastName + ", " + FirstMidName; }
        }

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

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

필수 및 표시 특성

속성의 LastName 특성은 필수 필드이고, 텍스트 상자에 대한 캡션 "성"(속성 이름 대신", 공백이 없는 "LastName")이어야 하며 값이 50자를 초과할 수 없음을 지정합니다.

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

StringLength 특성은 데이터베이스의 최대 길이를 설정하고 ASP.NET MVC에 대한 클라이언트 쪽 및 서버 쪽 유효성 검사를 제공합니다. 이 특성의 최소 문자열 길이를 지정할 수도 있지만, 최소값은 데이터베이스 스키마에 영향을 주지 않습니다. DateTime, int, double 및 float와 같은 값 형식에는 필수 특성 이 필요하지 않습니다. 값 형식은 null 값을 할당할 수 없으므로 기본적으로 필요합니다. Required 특성을 제거하고 특성에 대한 StringLength 최소 길이 매개 변수로 바꿀 수 있습니다.

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

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

public class Instructor
{
   public int InstructorID { 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; }

   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

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

FullName 계산 속성

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

public string FullName
{
    get { return LastName + ", " + FirstMidName; }
}

과정 및 OfficeAssignment 탐색 속성

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

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

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

OfficeAssignment 엔터티 만들기

OfficeAssignment_entity

다음 코드를 사용하여 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 또는 ID과 다른 classnameID 이름으로 지정하려는 경우에도 특성을 사용할 수 있습니다. 기본적으로 EF는 열이 식별 관계에 대한이므로 키를 데이터베이스에서 생성되지 않은 키로 처리합니다.

ForeignKey 특성

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

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

자습서의 뒷부분에서는 흐름 API와 이 관계를 구성하는 방법을 보여 줍니다.

강사 탐색 속성

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

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

강좌 엔터티 수정

Course_entity

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

      [Display(Name = "Department")]
      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 특성

속성에 CourseIDNone 매개 변수가 있는 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; }
    

부서 엔터티 만들기

Department_entity

다음 코드를 사용하여 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)]
      public DateTime StartDate { get; set; }

      [Display(Name = "Administrator")]
      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 유형에 매핑됩니다. 그러나 이 경우 열이 통화 금액을 보유하게 되며 money 데이터 형식이 더 적합하다는 것을 알고 있습니다.

외래 키 및 탐색 속성

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

  • 부서는 관리자가 있을 수도 있고 없을 수도 있으며, 관리자는 항상 강사입니다. 따라서 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 하지 않은 경우 이니셜라이저가 실행될 때 다음과 같은 예외 메시지가 표시됩니다. "참조 관계로 인해 허용되지 않는 순환 참조가 발생합니다." 비즈니스 규칙에 nullable이 아닌 속성이 필요한 InstructorID 경우 다음 흐름 API를 사용하여 관계에 대한 계단식 삭제를 사용하지 않도록 설정해야 합니다.

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

학생 엔터티 수정

Student_entity

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 StudentID { get; set; }

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

      [StringLength(50, MinimumLength = 1, 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)]
      [Display(Name = "Enrollment Date")]
      public DateTime EnrollmentDate { get; set; }

      public string FullName
      {
         get { return LastName + ", " + FirstMidName; }
      }

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

등록 엔터티

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)를 포함해야 합니다. 이 경우 데이터베이스에 페이로드 (또는 순수 조인 테이블)가 없는 다대다 조인 테이블에 해당하며, 이에 대한 모델 클래스를 전혀 만들 필요가 없습니다. InstructorCourse 엔터티는 이러한 종류의 다대다 관계를 가지며, 볼 수 있듯이 엔터티 클래스는 없습니다.

Instructor-Course_many-to-many_relationship

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

Instructor-Course_many-to-many_relationship_tables

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

관계를 보여 주는 엔터티 다이어그램

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

School_data_model_diagram

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

데이터베이스 컨텍스트에 코드를 추가하여 데이터 모델 사용자 지정

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

이 자습서에서는 특성으로는 수행할 수 없는 데이터베이스 매핑에만 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는 이 코드 없이 다대다 관계를 구성할 수 있지만, 이 코드를 호출하지 않으면 열과 같은 InstructorInstructorIDInstructorID 기본 이름이 표시됩니다.

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

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

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

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

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

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

namespace ContosoUniversity.Migrations
{
   using System;
   using System.Collections.Generic;
   using System.Data.Entity;
   using System.Data.Entity.Migrations;
   using System.Linq;
   using ContosoUniversity.Models;
   using ContosoUniversity.DAL;

   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").InstructorID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").InstructorID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").InstructorID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").InstructorID }
            };
         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").InstructorID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID, 
                    Location = "Thompson 304" },
            };
         officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, 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").StudentID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").StudentID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

         foreach (Enrollment e in enrollments)
         {
            var enrollmentInDataBase = context.Enrollments.Where(
                s =>
                     s.Student.StudentID == 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,
       Department = departments.Single( s => s.Name == "Engineering"),
       Instructors = new List<Instructor>() 
     },
     ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

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

마이그레이션 추가 및 데이터베이스 업데이트

PMC에서 명령을 입력합니다.add-migration

PM> add-Migration Chap4

이 시점에서 데이터베이스를 업데이트하려고 하면 다음 오류가 발생합니다.

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

<timestamp>_Chap4.cs 파일을 편집하고 다음 코드를 변경합니다(SQL 문을 추가하고 문을 수정 AddColumn 합니다).

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));
    AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true);
    CreateIndex("dbo.Course", "DepartmentID");
}

public override void Down()
{

(새 줄을 추가할 때 기존 AddColumn 줄을 주석으로 처리하거나 삭제해야 합니다. 또는 명령을 입력 update-database 하면 오류가 발생합니다.)

기존 데이터로 마이그레이션을 실행할 때 외래 키 제약 조건을 충족하기 위해 데이터베이스에 스텁 데이터를 삽입해야 하는 경우도 있습니다. 생성된 코드는 nullable이 아닌 DepartmentID 외래 키를 Course 테이블에 추가합니다. 코드가 실행 AddColumn 될 때 테이블에 행 Course 이 이미 있는 경우 SQL Server null일 수 없는 열에 넣을 값을 모르기 때문에 작업이 실패합니다. 따라서 새 열에 기본값을 제공하도록 코드를 변경했으며 기본 부서 역할을 하는 "Temp"라는 스텁 부서를 만들었습니다. 따라서 이 코드가 실행될 때 기존 Course 행이 있는 경우 모두 "Temp" 부서와 관련이 있습니다.

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

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

참고

데이터를 마이그레이션하고 스키마를 변경할 때 다른 오류가 발생할 수 있습니다. 마이그레이션 오류를 resolve 수 없는 경우 Web.config파일에서 연결 문자열을 변경하거나 데이터베이스를 삭제할 수 있습니다. 가장 간단한 방법은 Web.config 파일에서 데이터베이스의 이름을 바꾸는 것입니다. 예를 들어 다음과 같이 데이터베이스 이름을 CU_test 변경합니다.

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

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

이전처럼 Server Explorer 데이터베이스를 열고 테이블 노드를 확장하여 모든 테이블이 만들어졌는지 확인합니다. (이전 시점부터 서버 Explorer 열려 있는 경우 새로 고침 단추를 클릭합니다.)

서버 Explorer 데이터베이스를 보여 주는 스크린샷 테이블 노드가 확장됩니다.

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

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

Table_data_in_CourseInstructor_table

요약

이제 더 복잡한 데이터 모델 및 해당 데이터베이스가 만들어졌습니다. 다음 자습서에서는 관련 데이터에 액세스하는 다양한 방법에 대해 자세히 알아봅니다.

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