ASP.NET MVC 애플리케이션에서 Entity Framework를 사용하여 상속 구현(10개 중 8개)
작성자 : Tom Dykstra
Contoso University 샘플 웹 애플리케이션은 Entity Framework 5 Code First 및 Visual Studio 2012를 사용하여 ASP.NET MVC 4 애플리케이션을 만드는 방법을 보여 줍니다. 자습서 시리즈에 대한 정보는 시리즈의 첫 번째 자습서를 참조하세요.
참고
resolve 수 없는 문제가 발생하면 완료된 장을 다운로드하고 문제를 재현해 보세요. 일반적으로 코드를 완료된 코드와 비교하여 문제에 대한 해결 방법을 찾을 수 있습니다. 몇 가지 일반적인 오류 및 해결 방법은 오류 및 해결 방법을 참조하세요.
이전 자습서에서는 동시성 예외를 처리했습니다. 이 자습서에서는 데이터 모델에서 상속을 구현하는 방법을 보여 줍니다.
개체 지향 프로그래밍에서 상속을 사용하여 중복 코드를 제거할 수 있습니다. 이 자습서에서는 강사와 학생 모두에게 공통적인 속성(예: LastName
)이 포함된 Person
기본 클래스에서 클래스가 파생되도록 Instructor
및 Student
클래스를 변경합니다. 웹 페이지를 추가하거나 변경하지는 않지만 일부 코드를 변경하고 이러한 변경 내용이 데이터베이스에 자동으로 반영됩니다.
계층별 테이블 및 형식별 테이블 상속
개체 지향 프로그래밍에서는 상속을 사용하여 관련 클래스로 더 쉽게 작업할 수 있습니다. 예를 들어 Instructor
데이터 모델의 및 Student
클래스는 School
여러 속성을 공유하므로 중복 코드가 발생합니다.
Instructor
및 Student
엔터티에서 공유하는 속성에 대해 중복 코드를 제거하려고 한다고 가정해 보겠습니다. 다음 그림과 같이 이러한 공유 속성만 포함하는 기본 클래스를 만든 Person
다음, 및 Student
엔터티가 해당 기본 클래스에서 상속되도록 할 Instructor
수 있습니다.
데이터베이스에 이 상속 구조를 여러 가지 방법으로 나타낼 수 있습니다. 학생과 강사에 관한 정보를 하나의 테이블에 포함하는 Person
테이블을 포함할 수 있습니다. 일부 열은 강사()HireDate
에만 적용할 수 있으며, 일부는 학생(EnrollmentDate
)에만 적용되며, 일부는 둘 다(LastName
, FirstName
)에만 적용될 수 있습니다. 일반적으로 각 행이 나타내는 형식을 나타내는 판별 자 열이 있습니다. 예를 들어, 판별자 열은 강사에 대해 "Instructor"를, 학생에 대해 "Student"를 포함할 수 있습니다.
단일 데이터베이스 테이블에서 엔터티 상속 구조를 생성하는 이 패턴을 TPH( 테이블 단위 계층 ) 상속이라고 합니다.
다른 방법은 데이터베이스를 상속 구조와 유사하게 만드는 것입니다. 예를 들어 Person
테이블에 이름 필드만 있고 날짜 필드가 있는 별도의 Instructor
및 Student
테이블을 포함할 수 있습니다.
각 엔터티 클래스에 대한 데이터베이스 테이블을 만드는 이 패턴을 TPT( 형식별 테이블 ) 상속이라고 합니다.
TPT 패턴으로 인해 복잡한 조인 쿼리가 발생할 수 있으므로 TPH 상속 패턴은 일반적으로 TPT 상속 패턴보다 Entity Framework에서 더 나은 성능을 제공합니다. 이 자습서에서는 TPH 상속을 구현하는 방법을 보여 줍니다. 다음 단계를 수행하여 이 작업을 수행합니다.
- 클래스를
Person
만들고 에서 파생Person
되도록 및Student
클래스를 변경Instructor
합니다. - 데이터베이스 컨텍스트 클래스에 모델-데이터베이스 매핑 코드를 추가합니다.
- 프로젝트
PersonID
전체에서 및StudentID
참조를 로 변경InstructorID
합니다.
Person 클래스 만들기
참고: 이러한 클래스를 사용하는 컨트롤러를 업데이트할 때까지 아래 클래스를 만든 후에는 프로젝트를 컴파일할 수 없습니다.
Models 폴더에서 Person.cs를 만들고 템플릿 코드를 다음 코드로 바꿉니다.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public abstract class Person
{
[Key]
public int PersonID { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
[StringLength(50, MinimumLength = 1)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50, MinimumLength = 2, ErrorMessage = "First name must be between 2 and 50 characters.")]
public string FirstMidName { get; set; }
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
}
Instructor.cs에서 Person
클래스를 Instructor
파생하고 키 및 이름 필드를 제거합니다. 해당 코드는 다음 예제와 같이 나타납니다.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
}
Student.cs를 비슷하게 변경합니다. 클래스는 Student
다음 예제와 같습니다.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student : Person
{
[DataType(DataType.Date)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
모델에 사람 엔터티 형식 추가
SchoolContext.cs에서 엔터티 형식에 Person
대한 속성을 추가 DbSet
합니다.
public DbSet<Person> People { get; set; }
계층당 하나의 테이블 상속을 구성하기 위해 Entity Framework에 필요한 모든 작업입니다. 보듯이 데이터베이스를 다시 만들 Person
때 및 Instructor
테이블 대신 테이블이 Student
있습니다.
InstructorID 및 StudentID를 PersonID로 변경
SchoolContext.cs의 Instructor-Course 매핑 문에서 를 로 MapRightKey("PersonID")
변경 MapRightKey("InstructorID")
합니다.
modelBuilder.Entity<Course>()
.HasMany(c => c.Instructors).WithMany(i => i.Courses)
.Map(t => t.MapLeftKey("CourseID")
.MapRightKey("PersonID")
.ToTable("CourseInstructor"));
이 변경은 필요하지 않습니다. 다대다 조인 테이블의 InstructorID 열 이름을 변경합니다. 이름을 InstructorID로 두면 애플리케이션이 여전히 올바르게 작동합니다. 완성된 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; }
public DbSet<Person> People { 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("PersonID")
.ToTable("CourseInstructor"));
}
}
}
다음으로 Migrations 폴더의 타임스탬프를 적용한 마이그레이션 파일을 제외하고 프로젝트 전체에서 및 로 변경 StudentID
InstructorID
PersonID
PersonID
해야 합니다. 이렇게 하려면 변경해야 하는 파일만 찾아서 연 다음, 열린 파일에서 전역 변경을 수행합니다. 마이그레이션 폴더의 유일한 파일은 Migrations\Configuration.cs입니다.
-
중요
먼저 Visual Studio에서 열려 있는 모든 파일을 닫습니다.
찾기 및 바꾸기 -- 편집 메뉴에서 모든 파일 찾기를 클릭한 다음 가 포함된
InstructorID
프로젝트의 모든 파일을 검색합니다.각 파일에 대해 한 줄을 두 번 클릭하여 Migrations 폴더의 time-stamp>_.cs 마이그레이션 파일을 제외한<결과 찾기 창에서 각 파일을 엽니다.
파일에서 바꾸기 대화 상자를 열고 조회를 열려 있는 모든 문서로 변경합니다.
파일에서 바꾸기 대화 상자를 사용하여 모두
InstructorID
로 변경PersonID.
가 포함된 프로젝트의 모든 파일을 찾습니다
StudentID
.각 파일에 대해 한 줄을 두 번 클릭하여 Migrations 폴더의 <time-stamp>_*.cs 마이그레이션 파일을 제외한 결과 찾기 창에서 각 파일을 엽니다.
파일에서 바꾸기 대화 상자를 열고 조회를 열려 있는 모든 문서로 변경합니다.
파일에서 바꾸기 대화 상자를 사용하여 모두
StudentID
로 변경합니다PersonID
.프로젝트를 빌드합니다.
기본 키 이름을 지정하는 패턴의 classnameID
단점을 보여 줍니다. 클래스 이름을 접두사로 지정하지 않고 기본 키 ID 이름을 지정한 경우 이제 이름을 변경할 필요가 없습니다.)
마이그레이션 파일 만들기 및 업데이트
PMC(패키지 관리자 콘솔)에서 다음 명령을 입력합니다.
Add-Migration Inheritance
Update-Database
PMC에서 명령을 실행합니다. 마이그레이션에서 처리 방법을 모르는 기존 데이터가 있으므로 이 시점에서 명령이 실패합니다. 다음과 같은 오류가 표시됩니다.
ALTER TABLE 문이 FOREIGN KEY 제약 조건 "FK_dbo 충돌했습니다. Department_dbo. Person_PersonID". "ContosoUniversity" 테이블 "dbo" 데이터베이스에서 충돌이 발생했습니다. Person", 열 'PersonID'.
마이그레이션< timestamp>_Inheritance.cs 및 메서드를 Up
다음 코드로 바꿉니다.
public override void Up()
{
DropForeignKey("dbo.Department", "InstructorID", "dbo.Instructor");
DropForeignKey("dbo.OfficeAssignment", "InstructorID", "dbo.Instructor");
DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
DropForeignKey("dbo.CourseInstructor", "InstructorID", "dbo.Instructor");
DropIndex("dbo.Department", new[] { "InstructorID" });
DropIndex("dbo.OfficeAssignment", new[] { "InstructorID" });
DropIndex("dbo.Enrollment", new[] { "StudentID" });
DropIndex("dbo.CourseInstructor", new[] { "InstructorID" });
RenameColumn(table: "dbo.Department", name: "InstructorID", newName: "PersonID");
RenameColumn(table: "dbo.OfficeAssignment", name: "InstructorID", newName: "PersonID");
RenameColumn(table: "dbo.Enrollment", name: "StudentID", newName: "PersonID");
RenameColumn(table: "dbo.CourseInstructor", name: "InstructorID", newName: "PersonID");
CreateTable(
"dbo.Person",
c => new
{
PersonID = c.Int(nullable: false, identity: true),
LastName = c.String(maxLength: 50),
FirstName = c.String(maxLength: 50),
HireDate = c.DateTime(),
EnrollmentDate = c.DateTime(),
Discriminator = c.String(nullable: false, maxLength: 128),
OldId = c.Int(nullable: false)
})
.PrimaryKey(t => t.PersonID);
// Copy existing Student and Instructor data into new Person table.
Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, StudentId AS OldId FROM dbo.Student");
Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, HireDate, null AS EnrollmentDate, 'Instructor' AS Discriminator, InstructorId AS OldId FROM dbo.Instructor");
// Fix up existing relationships to match new PK's.
Sql("UPDATE dbo.Enrollment SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = Enrollment.PersonId AND Discriminator = 'Student')");
Sql("UPDATE dbo.Department SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = Department.PersonId AND Discriminator = 'Instructor')");
Sql("UPDATE dbo.OfficeAssignment SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = OfficeAssignment.PersonId AND Discriminator = 'Instructor')");
Sql("UPDATE dbo.CourseInstructor SET PersonId = (SELECT PersonId FROM dbo.Person WHERE OldId = CourseInstructor.PersonId AND Discriminator = 'Instructor')");
// Remove temporary key
DropColumn("dbo.Person", "OldId");
AddForeignKey("dbo.Department", "PersonID", "dbo.Person", "PersonID");
AddForeignKey("dbo.OfficeAssignment", "PersonID", "dbo.Person", "PersonID");
AddForeignKey("dbo.Enrollment", "PersonID", "dbo.Person", "PersonID", cascadeDelete: true);
AddForeignKey("dbo.CourseInstructor", "PersonID", "dbo.Person", "PersonID", cascadeDelete: true);
CreateIndex("dbo.Department", "PersonID");
CreateIndex("dbo.OfficeAssignment", "PersonID");
CreateIndex("dbo.Enrollment", "PersonID");
CreateIndex("dbo.CourseInstructor", "PersonID");
DropTable("dbo.Instructor");
DropTable("dbo.Student");
}
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에서 데이터베이스를 삭제하는 방법을 참조하세요. 이 방법을 사용하여 자습서를 계속 진행하려면 배포된 사이트가 자동으로 마이그레이션을 실행할 때 동일한 오류가 발생하므로 이 자습서의 끝에 있는 배포 단계를 건너뜁니다. 마이그레이션 오류를 해결하려는 경우 가장 좋은 리소스는 Entity Framework 포럼 또는 StackOverflow.com 중 하나입니다.
테스트
사이트를 실행하고 다양한 페이지를 사용해 보세요. 모든 항목이 이전과 같이 작동합니다.
서버 ExplorerSchoolContext를 확장한 다음 테이블을 확장하면 학생 및 강사 테이블이 Person 테이블로 대체된 것을 볼 수 있습니다. Person 테이블을 확장하면 학생 및 강사 테이블에 있는 모든 열이 표시됩니다.
Person 테이블을 마우스 오른쪽 단추로 클릭한 후 테이블 데이터 표시를 클릭하여 판별자 열을 표시합니다.
다음 다이어그램은 새 School 데이터베이스의 구조를 보여 줍니다.
요약
계층당 테이블 상속은 이제 , Student
및 Instructor
클래스에 Person
대해 구현되었습니다. 이 상속 구조 및 기타 상속 구조에 대한 자세한 내용은 Morteza Manavi의 블로그에서 상속 매핑 전략을 참조하세요. 다음 자습서에서는 리포지토리 및 작업 패턴 단위를 구현하는 몇 가지 방법을 살펴보겠습니다.
다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 콘텐츠 맵에서 찾을 수 있습니다.