ASP.NET MVC 애플리케이션에 대한 더 복잡한 데이터 모델 만들기(10개 중 4개)
작성자: Tom Dykstra
Contoso University 샘플 웹 애플리케이션은 Entity Framework 5 Code First 및 Visual Studio 2012를 사용하여 ASP.NET MVC 4 애플리케이션을 만드는 방법을 보여 줍니다. 자습서 시리즈에 대한 정보는 시리즈의 첫 번째 자습서를 참조하세요.
참고 항목
해결할 수 없는 문제가 발생하면 완료된 장을 다운로드하고 문제를 재현해 보세요. 일반적으로 코드를 완료된 코드와 비교하여 문제에 대한 솔루션을 찾을 수 있습니다. 몇 가지 일반적인 오류 및 해결 방법은 오류 및 해결 방법을 참조 하세요.
이전 자습서에서는 세 개의 엔터티로 구성된 간단한 데이터 모델을 사용했습니다. 이 자습서에서는 엔터티와 관계를 더 추가하고 서식, 유효성 검사 및 데이터베이스 매핑 규칙을 지정하여 데이터 모델을 사용자 지정합니다. 엔터티 클래스에 특성을 추가하고 데이터베이스 컨텍스트 클래스에 코드를 추가하여 데이터 모델을 사용자 지정하는 두 가지 방법이 표시됩니다.
완료되면 엔터티 클래스는 다음 그림에 표시된 완성된 데이터 모델을 구성하게 됩니다.
특성을 사용하여 데이터 모델 사용자 지정
이 섹션에서는 서식 지정, 유효성 검사 및 데이터베이스 매핑 규칙을 지정하는 특성을 사용하여 데이터 모델을 사용자 지정하는 방법을 배웁니다. 그런 다음 다음 몇 가지 섹션에서는 이미 만든 클래스에 특성을 추가하고 모델의 나머지 엔터티 형식에 대한 새 클래스를 만들어 전체 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 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 열거형은 날짜, 시간, 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)]
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
모든 보기도 마찬가지입니다.
The StringLengthAttribute
특성을 사용하여 데이터 유효성 검사 규칙 및 메시지를 지정할 수도 있습니다. 사용자가 이름을 50자 이하로 입력하였는지를 확인한다고 가정합니다. 이 제한을 추가하려면 다음 예제와 같이 StringLength 특성을 LastName
및 FirstMidName
속성에 추가합니다.
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 MaxLengthOnNames
명령은 timeStamp>_MaxLengthOnNames.cs 파일을 <만듭니다. 이 파일에는 현재 데이터 모델과 일치하도록 데이터베이스를 업데이트하는 코드가 포함되어 있습니다. 마이그레이션 파일 이름 앞에 추가된 타임스탬프는 Entity Framework에서 마이그레이션 순서를 지정하는 데 사용됩니다. 여러 마이그레이션을 만든 후 데이터베이스를 삭제하거나 마이그레이션을 사용하여 프로젝트를 배포하는 경우 모든 마이그레이션이 생성된 순서대로 적용됩니다.
만들기 페이지를 실행하고 50자보다 긴 이름을 입력합니다. 50자를 초과하는 즉시 클라이언트 쪽 유효성 검사에 오류 메시지가 즉시 표시됩니다.
열 특성
또한 특성을 사용하여 클래스 및 속성을 데이터베이스에 매핑하는 방법을 제어할 수 있습니다. 필드에 중간 이름이 포함될 수 있으므로 첫 번째 이름 필드에 이름 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
서버 탐색기(웹용 Express를 사용하는 경우 데이터베이스 탐색기)에서 Student 테이블을 두 번 클릭합니다.
다음 이미지는 처음 두 마이그레이션을 적용하기 전의 원래 열 이름을 보여 줍니다. 열 이름이 변경 FirstMidName
FirstName
되는 것 외에도 두 개의 이름 열이 길이에서 MAX
50자로 변경되었습니다.
이 자습서의 뒷부분에서 볼 수 있듯이 Fluent API를 사용하여 데이터베이스 매핑을 변경할 수도 있습니다.
참고 항목
이러한 엔터티 클래스를 모두 만들기 전에 컴파일하려고 하면 컴파일러 오류가 발생할 수 있습니다.
강사 엔터티 만들기
템플릿 코드를 다음 코드로 바꿔서 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; }
}
}
Student
및 Instructor
엔터티의 여러 속성은 동일합니다. 이 시리즈의 뒷부분에 있는 상속 구현 자습서에서는 상속을 사용하여 리팩터링하여 이 중복성을 제거합니다.
필수 및 표시 특성
속성의 LastName
특성은 필수 필드이고, 텍스트 상자의 캡션이 "성"(속성 이름 대신 공백이 없는 "LastName")이어야 하며 값이 50자를 초과할 수 없음을 지정합니다.
[Required]
[Display(Name="Last Name")]
[StringLength(50)]
public string LastName { get; set; }
StringLength 특성은 데이터베이스의 최대 길이를 설정하고 ASP.NET MVC에 대한 클라이언트 쪽 및 서버 쪽 유효성 검사를 제공합니다. 이 특성의 최소 문자열 길이를 지정할 수도 있지만, 최소값은 데이터베이스 스키마에 영향을 주지 않습니다. DateTime, int, double 및 float와 같은 값 형식에는 Required 특성이 필요하지 않습니다. 값 형식은 null 값을 할당할 수 없으므로 기본적으로 필요합니다. 필수 특성을 제거하고 특성에 대한 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 탐색 속성
Courses
및 OfficeAssignment
속성은 탐색 속성입니다. 앞에서 설명한 대로 지연 로드라는 Entity Framework 기능을 활용할 수 있도록 일반적으로 가상으로 정의됩니다. 또한 탐색 속성이 여러 엔터티를 보유할 수 있는 경우 해당 형식은 ICollection<T> 인터페이스를 구현해야 합니다. (예:IList<T>는 Add를 구현하지 않으므로 IEnumerable T> IEnumerable<T>
를 사용할 수<없습니다.
강사는 다양한 과정을 가르칠 수 있으므로 Courses
엔터티 컬렉션 Course
으로 정의됩니다. 비즈니스 규칙에 따르면 강사는 최대 하나의 사무실만 가질 수 있으므로 OfficeAssignment
단일 OfficeAssignment
엔터티로 정의됩니다(사무실이 할당되지 null
않은 경우일 수 있음).
public virtual ICollection<Course> Courses { get; set; }
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할 수 있는 복사 및 붙여넣기 오류를 만들지 않았는지 확인합니다.
키 특성
Instructor
및 OfficeAssignment
엔터티 간에는 일 대 영 또는 일 대 일 관계가 있습니다. 사무실 할당은 할당된 강사에 관하여 존재하며, 따라서 해당 기본 키도 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; }
[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 특성
속성에 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)]
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
유형에 매핑됩니다. 그러나 이 경우 열이 통화 금액을 보유하게 되며 통화 데이터 형식이 더 적합하다는 것을 알고 있습니다.
외래 키 및 탐색 속성
외래 키 및 탐색 속성은 다음과 같은 관계를 반영합니다.
부서는 관리자가 있을 수도 있고 없을 수도 있으며, 관리자는 항상 강사입니다. 따라서
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);
학생 엔터티 수정
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; }
다대다 관계
Student
및 Course
엔터티 간에는 다 대 다 관계가 있으며, Enrollment
엔터티는 데이터베이스에서 ‘페이로드가 있는’ 다 대 다 조인 테이블로 작동합니다. 즉 Enrollment
, 테이블에 조인된 테이블(이 경우 기본 키 및 Grade
속성)에 대한 외래 키 외에 추가 데이터가 포함됩니다.
다음 그림은 이러한 관계 모양을 엔터티 다이어그램으로 보여 줍니다. (이 다이어그램은 다음을 사용하여 생성되었습니다.Entity Framework Power Tools; 다이어그램 만들기는 자습서의 일부가 아니며 여기서 그림으로만 사용됩니다.)
각 관계 줄에는 한쪽 끝에 1, 다른 한쪽 끝에는 별표(*)가 있으며, 이는 일대다 관계를 나타냅니다.
Enrollment
테이블에 등급 정보가 포함되지 않은 경우 두 개의 외래 키(CourseID
및 StudentID
)를 포함해야 합니다. 이 경우 데이터베이스에 페이로드(또는 순수 조인 테이블)가 없는 다대다 조인 테이블에 해당하며, 모델 클래스를 만들 필요가 없습니다. Instructor
엔터티와 Course
엔터티는 다대다 관계를 가지며, 여기에서 볼 수 있듯이 엔터티 클래스는 없습니다.
그러나 다음 데이터베이스 다이어그램과 같이 데이터베이스에는 조인 테이블이 필요합니다.
Entity Framework는 자동으로 테이블을 만들고 CourseInstructor
, 테이블과 탐색 속성을 읽고 업데이트하여 간접적으로 읽고 업데이트 Instructor.Courses
Course.Instructors
합니다.
관계를 보여 주는 엔터티 다이어그램
다음 그림에는 Entity Framework 파워 도구가 완벽한 School 모델을 만드는 다이어그램이 나와 있습니다.
다대다 관계선(*~*) 및 일대다 관계선(1~*) 외에도, 강사와 OfficeAssignment
부서 엔터티 간의 Instructor
일대다 관계선(1~0..1)과 0.1~0.1 사이의 일대다 관계선(0..1에서 *)을 볼 수 있습니다.
데이터베이스 컨텍스트에 코드를 추가하여 데이터 모델 사용자 지정
다음으로 클래스에 새 엔터티를 SchoolContext
추가하고 Fluent 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는 이 코드 없이 다대다 관계를 구성할 수 있지만 호출하지 않으면 열과 같은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 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
합니다. 이렇게 하면 메서드를 사용하여 이와 Course
관련된 엔터티를 Instructors.Add
추가할 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’에서 충돌이 발생합니다.
<타임스탬프>_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
될 때 테이블에 행이 이미 있는 경우 SQL Server가 열에 Course
null이 될 수 없는 값을 모르기 때문에 작업이 실패합니다. 따라서 새 열에 기본값을 제공하도록 코드를 변경했으며 기본 부서 역할을 하도록 "Temp"라는 스텁 부서를 만들었습니다. 따라서 이 코드가 실행될 때 기존 Course
행이 있는 경우 모두 "Temp" 부서와 관련이 있습니다.
메서드가 Seed
실행되면 테이블에 행을 Department
삽입하고 기존 Course
행을 해당 새 Department
행과 연결합니다. UI에 강좌를 추가하지 않은 경우 더 이상 열에 "Temp" 부서 또는 기본값이 Course.DepartmentID
필요하지 않습니다. 다른 사용자가 애플리케이션을 사용하여 강좌를 추가했을 가능성을 허용하려면 열에서 기본값을 제거하고 "Temp" 부서를 삭제하기 전에 모든 Course
행(이전 메서드 Seed
실행에서 삽입한 행뿐만 아니라)이 유효한 DepartmentID
값을 갖도록 메서드 코드를 업데이트 Seed
하려고 합니다.
타임스탬프_Chap4.cs 파일 편집<을 완료한 후 PMC에 명령을 입력 update-database
하여 마이그레이션을 실행합니다.>
참고 항목
데이터를 마이그레이션하고 스키마를 변경할 때 다른 오류가 발생할 수 있습니다. 해결할 수 없는 마이그레이션 오류가 발생하면 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에서 데이터베이스를 삭제하는 방법을 참조하세요.
이전과 마찬가지로 서버 탐색기에서 데이터베이스를 열고 테이블 노드를 확장하여 모든 테이블이 만들어졌는지 확인합니다. (계속 있는 경우이전 시간부터 서버 탐색기를 열고 새로 고침 단추를 클릭합니다.)
테이블에 대한 CourseInstructor
모델 클래스를 만들지 않았습니다. 앞에서 설명한 대로 이 테이블은 엔터티와 Course
엔터티 간의 Instructor
다대다 관계에 대한 조인 테이블입니다.
테이블을 마우스 오른쪽 단추로 클릭하고 CourseInstructor
테이블 데이터 표시를 선택하여 탐색 속성에 추가 Course.Instructors
한 엔터티의 Instructor
결과로 테이블 데이터에 데이터가 있는지 확인합니다.
요약
이제 더 복잡한 데이터 모델 및 해당 데이터베이스가 만들어졌습니다. 다음 자습서에서는 관련 데이터에 액세스하는 다양한 방법에 대해 자세히 알아봅니다.
다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 콘텐츠 맵에서 찾을 수 있습니다.