파트 1: MVC 5를 이용한 Entity Framework 6 Code First 시작하기

등록일시: 2015-10-26 08:00,  수정일시: 2016-09-02 09:11
조회수: 24,001
이 문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본 자습서 시리즈에서는 가상의 Contoso University를 위한 예제 웹 응용 프로그램을 구축하는 과정들을 단계별로 살펴보면서 Entity Framework 6와 Visual Studio 2013을 이용해서 ASP.NET MVC 5 응용 프로그램을 구현하는 방법을 보여줍니다.

전체 프로젝트 다운로드PDF 다운로드

본 자습서의 Contoso University 예제 웹 응용 프로그램은 Entity Framework 6와 Visual Studio 2013을 이용해서 ASP.NET MVC 5 응용 프로그램을 구축하는 방법을 보여줍니다. 참고로 본 자습서에서는 Code First 접근 방식을 사용하고 있는데, Code First 접근 방식과 Database First 접근 방식, 그리고 Model First 접근 방식 중에서 가장 적절한 접근 방식을 선택하는 방법에 관한 정보는 MSDN의 Entity Framework Development Workflows 기사를 참고하시기 바랍니다.

이 예제 응용 프로그램은 가상의 Contoso University를 위한 웹 사이트로, 학생의 입학, 강의 생성, 그리고 강사 배정 같은 기능들을 제공합니다. 본 자습서 시리즈에서는 이 Contoso University 예제 응용 프로그램을 구현하는 각각의 과정들을 단계별로 살펴봅니다. 또는 미리 준비된 전체 응용 프로그램을 다운로드 받아서 살펴볼 수도 있습니다.

그리고 Mike Brind가 본 자습서의 내용을 Visual Basic 버전으로 변환한 동일한 내용을 Mikesdotnetting 사이트의 MVC 5 with EF 6 in Visual Basic 포스팅 시리즈를 통해서 살펴보실 수도 있으므로 참고하시기 바랍니다.

본 자습서에 사용된 소프트웨어의 버전

본 자습서 시리즈에서 살펴보게 될 내용들은 Visual Studio Express 2013 for Web이나 Visual Studio 2012에서도 정상적으로 동작합니다. 다만 Visual Studio 2012를 이용한 Windows Azure 배포 과정에는 Visual Studio 2012용 Windows Azure SDK가 필요합니다.

역주: 저는 이 자습서를 번역하면서 Visual Studio 2015 버전을 이용해서 예제 응용 프로그램을 테스트해보고 있습니다. 만약 본문의 내용과 다르게 처리해야 하는 부분이 발견되면, 그때마다 추가로 설명을 보충하도록 하겠습니다.

자습서의 버전

본 자습서의 이전 버전이 궁금하신 분들은 EF 4.1 / MVC 3 e-bookGetting Started with EF 5 using MVC 4 자습서를 참고하시기 바랍니다.

질문과 의견

본 자습서의 내용 중 만족스러웠던 점이나 개선요청 등의 의견이 있으시면 원문 페이지 하단의 의견(Comments) 절에 피드백으로 남겨주시기 바랍니다. 본 자습서와 직접 관련이 없는 질문은 ADO.NET, Entity Framework, LINQ to SQL, NHibernate 포럼이나 ADO.NET Entity Framework and LINQ to Entities 포럼, 또는 StackOverflow.com에 올려주시기 바랍니다.

직접 해결하기 어려운 문제가 발생할 경우, 다운로드 받은 전체 프로젝트의 코드와 여러분이 자습서를 따라서 작성한 코드를 비교해보기만 해도 대부분 문제의 해결방법을 발견할 수 있을 것입니다. 그 밖의 몇 가지 일반적인 오류들이나 그에 다른 해결 방법에 대해서는 Common errors, and solutions or workarounds for them 절을 참고하시기 바랍니다.

Contoso University 웹 응용 프로그램

본 자습서 시리즈에서 만들어볼 예제 응용 프로그램은 간단한 대학 웹 사이트로, 사용자가 학생, 강의, 그리고 강사 정보를 조회하거나 수정할 수 있는 사이트입니다. 다음 그림들은 앞으로 구현해보게 될 몇 가지 화면들을 미리 보여주고 있습니다.

이 사이트의 사용자 인터페이스 스타일은 내장 템플릿에서 자동으로 생성되는 스타일을 크게 손대지 않고 대부분 그대로 사용하고 있습니다. 이는 Entity Framework의 사용 방법에 관해서만 집중적으로 살펴보기 위한 것입니다.

전제조건

본문의 도입부에 정리된 본 자습서에 사용된 소프트웨어의 버전 절을 참고하시기 바랍니다. 그러나 Entity Framework 6는 자습서 내용의 과정 중, EF NuGet 패키지를 설치하는 단계가 포함되어 있으므로 미리 설치할 필요가 없습니다.

MVC 웹 응용 프로그램 생성하기

먼저 Visual Studio를 실행하고 "ContosoUniversity"라는 이름으로 새로운 C# 웹 프로젝트를 생성합니다.

그런 다음, 새 ASP.NET 프로젝트(New ASP.NET Project) 대화 상자에서 MVC 템플릿을 선택합니다. 만약 Microsoft Azure 영역의 클라우드의 호스트(Host in the cloud) 체크 박스가 선택되어 있다면 이 체크는 해제합니다.

계속해서 이번에는 인증 변경(Change Authentication) 버튼을 클릭합니다.

그리고 인증 변경(Change Authentication) 대화 상자가 나타나면 인증 안 함(No Authentication)을 선택하고 확인(OK) 버튼을 클릭합니다. 본 자습서에서는 사용자의 로그인을 강제하거나 로그인 한 사용자에 따라 접근을 제한하지 않기 때문입니다.

마지막으로 새 ASP.NET 프로젝트(New ASP.NET Project) 대화 상자에서 확인(OK) 버튼을 클릭하면 프로젝트가 생성됩니다.

사이트 스타일 설정하기

몇 가지 간단한 변경을 통해서 사이트의 메뉴와 레이아웃, 그리고 홈 페이지를 설정해보겠습니다.

우선 Views\Shared\_Layout.cshtml  파일을 열고 다음과 같은 부분들을 변경합니다:

  • "My ASP.NET Application"이나 "Application name"이라고 작성되어 있는 부분들을 모두 "Contoso University"로 변경합니다.
  • 학생(Students), 강의(Courses), 강사(Instructors), 그리고 학과(Departments)에 대한 메뉴 항목들을 추가하고, 대신 연락처 메뉴는 제거합니다.

변경된 부분들이 다음 코드에 강조되어 있습니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - Contoso University</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="navbar-inner">
            <div class="container">
                <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Contoso University", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
                <div class="nav-collapse collapse">
                    <ul class="nav">
                        <li>@Html.ActionLink("Home", "Index", "Home")</li>
                        <li>@Html.ActionLink("About", "About", "Home")</li>
                        <li>@Html.ActionLink("Students", "Index", "Student")</li>
                        <li>@Html.ActionLink("Courses", "Index", "Course")</li>
                        <li>@Html.ActionLink("Instructors", "Index", "Instructor")</li>
                        <li>@Html.ActionLink("Departments", "Index", "Department")</li>
                    </ul>
                </div>
            </div>
        </div>
    </div>

    <div class="container">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - Contoso University</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

이번에는 Views\Home\Index.cshtml  파일을 열고, 파일 내용을 다음 코드와 같이 변경해서 ASP.NET과 MVC에 대한 문구들을 예제 응용 프로그램에 대한 문구들로 대체합니다:

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>Contoso University is a sample application that
        demonstrates how to use Entity Framework 6 in an 
        ASP.NET MVC 5 web application.</p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in the tutorial series on the ASP.NET site.</p>
        <p><a class="btn btn-default" href="http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from the Microsoft Code Gallery.</p>
        <p><a class="btn btn-default" href="http://code.msdn.microsoft.com/ASPNET-MVC-Application-b01a9fe8">Download &raquo;</a></p>
    </div>
</div>

이제 CTRL+F5 키를 눌러서 사이트를 실행해봅니다. 그러면 다음과 같이 변경된 메인 메뉴가 적용된 홈 페이지가 나타날 것입니다.

Entity Framework 6 설치하기

도구(Tools) 메뉴에서 NuGet 패키지 관리자(NuGet Package Manager)를 클릭한 다음, 패키지 관리자 콘솔(Package Manager Console)을 클릭합니다.

그리고 패키지 관리자 콘솔(Package Manager Console) 창에 다음 명령을 입력합니다:

Install-Package EntityFramework

이 그림은 6.0.0 버전이 설치된 모습을 보여주고 있습니다. 그러나 여러분이 직접 명령을 실행시켜보면 NuGet이 자동으로 (시험판 버전을 제외한) 가장 최신 버전의 Entity Framework를 설치해줄 것입니다. 참고로 본 자습서가 작성된 시점에는 버전 6.1.1이 가장 최신 버전이었습니다.

이 작업은 본 자습서에서 여러분이 수작업으로 수행하게 되는 몇 가지 않되는 작업들 중 하나로, 사실 ASP.NET MVC의 스캐폴딩 기능을 이용하면 자동으로 처리할 수도 있는 작업입니다. 그럼에도 불구하고 이렇게 직접 수작업으로 수행하는 이유는 Entity Framework를 사용하기 위해서 요구되는 과정들을 명시적으로 살펴보기 위한 것입니다. 본문의 뒷부분에서는 스캐폴딩을 이용해서 MVC 컨트롤러와 뷰를 생성해보게 될 것입니다. 또 다른 방법으로 아예 스캐폴딩을 이용해서 EF NuGet 패키지의 설치와 데이터베이스 컨텍스트 생성, 연결 문자열 생성 작업 등을 모두 자동으로 처리할 수도 있습니다. 이 방법을 선택할 경우 (충분한 이해를 갖춘 상태에서), 여러분이 해야할 일은 이런 복잡한 중간 과정들은 모두 생략하고, 필요한 엔터티 클래스들을 생성한 다음 MVC 컨트롤러를 스캐폴딩 하는 것 뿐입니다.

데이터 모델 생성하기

계속해서 이번에는 Contoso University 응용 프로그램에 사용될 엔터티 클래스들을 만들어보겠습니다. 즉 다음과 같은 세 가지 엔터티들을 생성하게 될 것입니다:

이 다이어그램을 살펴보면 Student 엔터티와 Enrollment 엔터티가 서로 일대다 관계를 갖고 있으며, Course 엔터티와 Enrollment 엔터티 역시 일대다 관계를 갖고 있음을 알 수 있습니다. 다시 말해서, 특정 학생은 제한 없이 원하는 만큼 강의를 수강할 수 있으며, 특정 강의를 수강할 수 있는 학생들의 수에도 제한이 없는 것입니다.

다음 절들에서는 실제로 이 엔터티들에 대한 각각의 클래스들을 작성해볼 것입니다.

노트: 이 엔터티 클래스들을 모두 작성하기 전에 프로젝트를 컴파일하면 컴파일러 오류가 발생합니다.

Student 엔터티

먼저 Models 폴더에 Student.cs 라는 이름의 클래스 파일을 생성하고, 자동으로 생성된 템플릿 코드를 다음 코드로 대체합니다:

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

이 클래스에서 ID 속성은 이 클래스에 대응하는 데이터베이스 테이블의 기본 키 컬럼이 됩니다. 기본적으로 Entity Framework는 속성 이름이 ID이거나 classnameID인 속성을 기본 키로 간주합니다.

그리고 Enrollments 속성은 탐색 속성(Navigation Property) 입니다. 탐색 속성은 해당 엔터티와 관계를 갖고 있는 다른 엔터티들과의 연결 고리 역할을 해줍니다. 즉, Student 엔터티의 Enrollments 속성은 해당 Student 엔터티와 관계를 맺고 있는 모든 Enrollment 엔터티들을 담고 있게 됩니다. 다시 말해서, 만약 데이터베이스의 특정 Student 로우가 두 개의 Enrollment 로우와 관계를 갖고 있다면(즉, 이 로우들의 StudentID 외래 키 컬럼에 해당 학생의 기본 키 값이 담겨 있다면), 해당 Student 엔터티의 Enrollments 탐색 속성에는 그 두 로우의 Enrollment 엔터티들이 담겨 있게 됩니다.

일반적으로 탐색 속성은 지연 로딩(Lazy Loading) 같은 특정 Entity Framework 기능의 이점을 활용할 수 있도록 virtual 키워드로 정의됩니다. (지연 로딩에 관해서는 본 자습서 시리즈 중 관계를 갖고 있는 데이터 읽기 파트에서 다시 살펴봅니다.)

또한 탐색 속성이 복수의 엔터티들을 담을 수 있어야 한다면 (다대다 관계나 일대다 관계의 경우), 반드시 해당 속성은 엔터티들이 추가되고, 삭제되고, 갱신될 수 있는 ICollection 같은 목록 형식이어야 합니다.

Enrollment 엔터티

이번에는 Models 폴더에 Enrollment.cs 라는 이름의 클래스를 생성하고 다음 코드로 기존 코드를 대체합니다:

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; }
        public Grade? Grade { get; set; }
        
        public virtual Course Course { get; set; }
        public virtual Student Student { get; set; }
    }
}

이 클래스에서는 EnrollmentID 속성이 기본 키가 됩니다. 즉, 이 엔터티는 Student 엔터티와는 달리 ID 대신 classnameID 패턴의 기본 키 속성을 사용하고 있는 것입니다. 일반적으로 이 두 가지 패턴 중 한 가지를 선택해서 데이터 모델 전체에 일관적으로 사용하는 경우가 대부분이지만, 본문에서는 여러분이 선택할 수 있는 다양한 방법들을 보여주기 위해서 일부러 다른 패턴들을 섞어서 사용하고 있습니다. 본 자습서 시리즈의 뒷부분에서는 데이터 모델에서 classname 부분 없이 ID만 사용해서 손쉽게 상속을 구현하는 방법도 살펴봅니다.

그리고 Grade 속성은 열거형(enum)으로, Grade 형식 선언의 뒷부분에 추가된 물음표는 Grade 속성이 nullable임을 나타내줍니다. 다시 말해서, 값이 null인 학점(Grade)은 0점이라는 뜻이 아니라, 학점을 알 수 없거나 아직 지정되지 않았음을 뜻합니다.

또한 StudentID 속성은 외래 키로, 이에 대응하는 탐색 속성은 Student입니다. Enrollment 엔터티는 하나의 Student 엔터티와만 관계를 맺을 수 있으므로, 이 속성은 단일 Student 엔터티만 담을 수 있습니다 (이전 절에서 살펴봤던, 복수의 Enrollment 엔터티들을 담을 수 있었던 Student.Enrollments 탐색 속성과는 다릅니다).

그리고 CourseID 속성 역시 외래 키로, 대응하는 탐색 속성은 Course입니다. Enrollment 엔터티는 하나의 Course 엔터티와만 관계를 맺을 수 있습니다.

Entity Framework는 특정 속성의 이름이 <탐색 속성 이름><기본 키 속성 이름> 패턴으로 구성되어 있으면 해당 속성을 외래 키로 해석합니다 (예를 들어, Student 엔터티의 기본 키는 ID이므로 Student 탐색 속성에 대한 외래 키 속성의 이름은 StudentID입니다). 아니면 간단하게 <기본 키 속성 이름>을 그대로 외래 키 속성 이름으로 사용할 수도 있습니다 (예를 들어, 바로 다음 절에서 살펴보게 될 Course 엔터티의 기본 키는 CourseID이므로 동일한 이름인 CourseID를 외래 키 이름으로 사용할 수도 있습니다).

Course 엔터티

이번에는 Models 폴더에 Course.cs 라는 이름으로 클래스를 생성하고 템플릿 코드를 다음의 코드로 대체합니다:

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

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

이 클래스에서 Enrollments 속성은 탐색 속성입니다. 특정 Course 엔터티는 개수의 제한 없이 Enrollment 엔터티들과 관계를 맺을 수 있습니다.

여기에 사용된 DatabaseGenerated 어트리뷰트에 대해서는 본 자습서 시리즈의 뒷 부분에서 더 자세하게 살펴보겠습니다. 간단히 설명하자면 이 어트리뷰트는 강의(Course)의 기본 키 값을 데이터베이스가 자동으로 생성하는 대신 직접 입력할 수 있게 해줍니다.

데이터베이스 컨텍스트 생성하기

지금까지 살펴본 데이터 모델들을 Entity Framework에서 제공되는 기능을 이용해서 조정하는 역할을 담당하는 주 클래스를 데이터베이스 컨텍스트(Database Context) 클래스라고 합니다. 데이터베이스 컨텍스트 클래스는 System.Data.Entity.DbContext 클래스를 상속 받아서 생성할 수 있으며, 이 클래스의 코드를 통해서 데이터 모델에 포함될 엔터티들을 지정하게 됩니다. 또한 Entity Framework의 동작을 사용자 지정할 수도 있습니다. 이번 프로젝트에 사용될 데이터베이스 컨텍스트 클래스의 이름은 SchoolContext입니다.

계속해서 이번에는 솔루션 탐색기(Solution Explorer)에서 ContosoUniversity 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가(Add)를 클릭한 다음, 다시 새 폴더(New Folder)를 클릭해서 새로운 폴더를 추가합니다. 그리고 폴더명을 DAL(Data Access Layer) 라고 지정합니다. 이 폴더에 SchoolContext.cs 라는 이름으로 새로운 클래스 파일을 생성하고, 템플릿 코드를 다음의 코드로 대체합니다:

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

namespace ContosoUniversity.DAL
{
    public class SchoolContext : DbContext
    {
        public SchoolContext() : base("SchoolContext")
        {
        }
        
        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

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

엔터티 집합 지정하기

이 클래스의 코드에서는 각각의 엔터티 집합에 대한 DbSet 속성을 생성하고 있습니다. 일반적으로 Entity Framework의 용어에서 엔터티 집합(Entity Set) 은 데이터베이스의 테이블에 대응하고, 엔터티(Entity) 는 테이블의 로우에 대응합니다.

노트: 이 클래스에서 DbSet<Enrollment>DbSet<Course> 관련 구문들을 생략할 수도 있으며, 그렇더라도 응용 프로그램은 여전히 정상적으로 동작합니다. 그 이유는 Student 엔터티가 Enrollment 엔터티를 참조하고, Enrollment 엔터티가 다시 Course 엔터티를 참조하고 있기 때문에 Entity Framework가 암시적으로 해당 엔터티들을 포함시키기 때문입니다.

연결 문자열 지정하기

연결 문자열의 이름은 이 클래스의 생성자에 매개변수로 전달됩니다 (연결 문자열은 잠시 뒤에 Web.config 파일에 추가하게 됩니다).

public SchoolContext() : base("SchoolContext")
{
}

이렇게 Web.config 파일에 저장되어 있는 연결 문자열들의 이름 중 하나를 전달하는 대신 연결 문자열 자체를 전달할 수도 있습니다. 사용하고자 하는 데이터베이스를 지정하는 다양한 방법들에 관한 더 많은 정보는 Entity Framework - Connections and Models 문서를 참고하시기 바랍니다.

명시적으로 연결 문자열 자체나 연결 문자열 이름을 지정하지 않으면 Entity Framework는 클래스 이름과 동일한 연결 문자열 이름을 사용하고자 하는 것으로 간주합니다. 따라서 이 예제 코드의 기본 연결 문자열 이름은 명시적으로 지정한 것과 동일한 SchoolContext가 될 것입니다.

단수형 테이블 이름 지정하기

마지막으로 OnModelCreating 메서드에 작성된 modelBuilder.Conventions.Remove 구문은 테이블 이름이 복수형으로 만들어지는 것을 막습니다. 이 구문을 지정하지 않으면 각각 Students, Courses, 그리고 Enrollments 라는 이름으로 데이터베이스에 테이블들이 생성될 것입니다. 그러나 본문의 예제에서는 복수형 이름 대신 Student, Course, 그리고 Enrollment라는 이름으로 테이블들이 생성됩니다. 물론 개발자에 따라 단수형 테이블 이름을 선호할 수도 있고, 복수형 테이블 이름을 선호할 수도 있습니다. 그러나 여기서는 단수형 이름을 사용하고 있는데, 요점은 이 코드 줄을 추가하거나 생략함으로써 선호하는 형식을 선택할 수 있다는 사실입니다.

테스트 데이터로 데이터베이스가 초기화되도록 EF 설정하기

Entity Framework는 응용 프로그램이 실행되는 시점에 자동으로 데이터베이스를 생성할 수 (또는 드랍시킨 다음 다시 생성할 수) 있습니다. 그리고 매번 응용 프로그램이 실행될 때마다 이 작업이 수행되어야 할지, 아니면 모델과 기존 데이터베이스가 일치하지 않는 경우에만 조건적으로 수행되어야 할지를 지정할 수도 있습니다. 그뿐만 아니라 Entity Framework가 데이터베이스를 생성한 다음, 데이터베이스에 테스트 데이터를 채워넣기 위해서 호출할 수 있는 Seed 메서드를 작성할 수도 있습니다.

기본 동작은 데이터베이스가 존재하지 않는 경우에만 데이터베이스를 생성하는 것입니다 (데이터베이스가 이미 생성되어 있고 모델이 변경된 경우에는 예외가 던져집니다). 이번 절에서는 모델이 변경될 때마다 데이터베이스가 드랍됐다가 다시 생성되도록 지정해보겠습니다. 단연한 얘기지만 이렇게 데이터베이스를 드랍시키게 되면 모든 데이터가 사라지게 됩니다. 물론 개발 도중에는 데이터베이스가 다시 생성될 때 Seed 메서드가 실행되어 테스트 데이터가 다시 만들어지기 때문에 대부분 아무런 문제가 되지 않습니다. 그러나 운영 환경에서조차 데이터베이스의 스키마를 변경해야 할 때마다 매번 모든 데이터가 사라지기를 원하는 개발자는 없을 것입니다. 본 자습서의 후반부 과정에서는 데이터베이스를 드랍시키고 다시 생성하는 대신, Code First 마이그레이션을 이용해서 데이터베이스의 스키마를 변경해서 모델의 변경 사항들을 처리하는 방법을 살펴보게 될 것입니다.

다시 돌아와서 DAL 폴더에 SchoolInitializer.cs 라는 이름으로 새로운 클래스 파일을 생성한 다음, 자동으로 생성된 템플릿 코드를 필요할 때마다 데이터베이스를 다시 생성하고 새로운 데이터베이스에 테스트 데이터를 적재해주는 다음의 코드로 대체합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class SchoolInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<SchoolContext>
    {
        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            students.ForEach(s => context.Students.Add(s));
            context.SaveChanges();
            
            var courses = new List<Course>
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3,},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3,},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3,},
                new Course{CourseID=1045,Title="Calculus",Credits=4,},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4,},
                new Course{CourseID=2021,Title="Composition",Credits=3,},
                new Course{CourseID=2042,Title="Literature",Credits=4,}
            };
            courses.ForEach(s => context.Courses.Add(s));
            context.SaveChanges();

            var enrollments = new List<Enrollment>
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050,},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            enrollments.ForEach(s => context.Enrollments.Add(s));
            context.SaveChanges();
        }
    }
}

Seed 메서드의 코드는 입력 매개변수로 전달받은 데이터베이스 컨텍스트 개체를 이용해서 데이터베이스에 새로운 엔터티들을 추가합니다. 각 엔터티 형식들에 대해 새로운 엔터티들의 컬랙션을 생성하고 이를 적절한 DbSet 속성에 추가한 다음, 변경 사항들을 데이터베이스에 저장합니다. 참고로 이 코드에 작성한 것처럼 각 엔터티 그룹에 대한 작업을 마칠 때마다 반드시 SaveChanges 메서드를 호출해야 하는 것은 아니지만, 이런 방식을 사용하면 코드에서 데이터베이스에 기록하는 도중에 예외가 발생할 경우, 문제가 발생한 소스의 위치를 찾기가 용이합니다.

그리고 Entity Framework에게 이 이니셜라이저 클래스를 사용하도록 지시하려면, 응용 프로그램의 Web.config 파일에 (프로젝트의 루트 폴더에 위치한) 구성되어 있는 entityFramework 요소에 다음과 같은 요소를 추가해야 합니다:

<entityFramework>
  <contexts>
    <context type="ContosoUniversity.DAL.SchoolContext, ContosoUniversity">
      <databaseInitializer type="ContosoUniversity.DAL.SchoolInitializer, ContosoUniversity" />
    </context>
  </contexts>
  <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
    <parameters>
      <parameter value="v11.0" />
    </parameters>
  </defaultConnectionFactory>
  <providers>
    <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
  </providers>
</entityFramework>

여기서 contexts 요소 하위의 context 요소의 type 어트리뷰트에는 정규화된 컨텍스트 클래스의 이름과 해당 클래스가 담긴 어셈블리를 지정하고, 그 하위의 databaseinitializer 요소의 type 어트리뷰트에는 이니셜라이저 클래스의 정규화된 이름과 해당 클래스가 담긴 어셈블리를 지정합니다. (EF가 이니셜라이저를 사용하지 않도록 하려면, context 요소에 disableDatabaseInitialization="true" 요소를 추가합니다.) 더 자세한 정보는 Entity Framework - Config File Settings 문서를 참고하시기 바랍니다.

그러나 이렇게 Web.config 파일에 이니셜라이저를 설정하는 방법도 있지만, Database.SetInitializer 구문을 Global.asax.cs 파일의 Application_Start 메서드에 추가해서 코드로 설정하는 방법도 있습니다. 보다 자세한 정보는 Understanding Database Initializers in Entity Framework Code First 문서를 참고하시기 바랍니다.

이제 응용 프로그램을 실행하고 데이터베이스에 첫 번째로 접근할 때 매번 Entity Framework가 데이터베이스와 모델(SchoolContext 및 엔터티 클래스들)을 비교하도록 응용 프로그램의 설정이 완료되었습니다. 만약 그 결과가 서로 다를 경우, 응용 프로그램이 데이터베이스를 드랍시켰다가 다시 생성하게 됩니다.

노트: 응용 프로그램을 운영 웹 서버에 배포할 때는 반드시 데이터베이스를 드랍시키고 재생성하는 코드를 제거하거나 비활성화시켜야 합니다. 구체적인 방법에 대해서는 본 자습서 시리즈의 이후 과정에서 살펴보겠습니다.

SQL Server Express LocalDB 데이터베이스를 사용하도록 EF 설정하기

LocalDB는 SQL Server Express 데이터베이스 엔진의 경량 버전입니다. 설치와 구성이 쉽고, 필요할 때만 시작되며, 사용자 모드에서 실행됩니다. LocalDB는 .mdf  파일 형태의 데이터베이스를 이용해서 작업할 수 있는 SQL Server Express의 특수한 실행 모드에서 동작합니다. 데이터베이스를 프로젝트와 함께 복사할 수 있도록 LocalDB 데이터베이스 파일들을 웹 프로젝트의 App_Data 폴더에 위치시킬 수도 있습니다. SQL Server Express의 사용자 인스턴스(User Instance) 기능을 통해서도 .mdf  파일을 이용한 작업은 가능하지만, 사용자 인터페이스 기능은 더 이상 사용되지 않습니다. 따라서 .mdf  파일을 이용한 작업에는 LocalDB를 사용하는 것을 권장합니다. Visual Studio 2012 및 이후의 버전들에서는 Visual Studio와 함께 LocalDB가 기본으로 설치됩니다.

대부분 운영용 웹 응용 프로그램에서는 SQL Server Express를 사용하지 않는 것이 일반적입니다. 특히 LocalDB는 운영 환경의 웹 응용 프로그램에서는 더욱 추천되지 않는데, 그 이유는 IIS와 함께 사용되도록 설계되지 않은 제품이기 때문입니다.

본 자습서에서는 LocalDB를 이용해서 작업을 진행합니다. 응용 프로그램의 Web.config 파일을 열고, 다음 예제와 같이 appSettings 요소 앞쪽에 connectionStrings 요소를 추가합니다. (반드시 프로젝트의 루트 폴더에 위치한 Web.config 파일을 수정해야 합니다. 그 하위 폴더인 Views 폴더에도 Web.config 파일이 존재하지만, 이 파일은 수정할 필요가 없습니다.)

<connectionStrings>
  <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity1;Integrated Security=SSPI;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<appSettings>
  <add key="webpages:Version" value="3.0.0.0" />
  <add key="webpages:Enabled" value="false" />
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

여기에 추가된 연결 문자열은 Entity Framework에게 ContosoUniversity1.mdf 라는 이름의 LocalDB를 사용하도록 지시합니다. (이 데이터베이스는 아직 존재하지 않습니다만, 잠시 후 EF가 자동으로 생성하게 됩니다.) 만약 데이터베이스를 프로젝트의 App_Data 폴더에 생성하고 싶다면, 연결 문자열에 AttachDBFilename=|DataDirectory|\ContosoUniversity1.mdf라고 추가하면 됩니다. 연결 문자열에 관한 더 자세한 정보는 SQL Server Connection Strings for ASP.NET Web Applications를 참고하시기 바랍니다.

그러나 무조건 이렇게 Web.config 파일에 연결 문자열을 지정해야만 하는 것은 아닙니다. 명시적으로 연결 문자열을 지정하지 않으면, 컨텍스트 클래스를 기반으로 Entity Framework가 기본적인 연결 문자열 구성을 사용하게 됩니다. 보다 자세한 정보는 Code First to a New Database 문서를 참고하시기 바랍니다.

역주: 개발용 머신에 Visual Studio 2015만 설치되어 있는 경우에는 12.0 버전의 LocalDB가 설치되어 있게 됩니다. 따라서 본문의 연결 문자열을 사용하면 데이터베이스를 찾지 못합니다. 이 문제점을 수정하려면 인스턴스 이름을 올바른 이름으로 변경해줘야 합니다. 가령, 저는 Data Source=(LocalDb)\v11.0; 부분을 Data Source=(LocalDb)\MSSQLLocalDB;로 변경해서 문제를 해결했습니다. 자신의 개발 머신에 설치되어 있는 LocalDB의 인스턴스 정보를 알고 싶다면 명령 프롬프트에서 SQLLocalDB info 명령을 입력하면 됩니다.

Student 컨트롤러 및 뷰 생성하기

이번에는 데이터를 출력할 웹 페이지를 만들어 보겠습니다. 바로 이 웹 페이지가 데이터를 요청하는 과정 중에 자연스럽게 데이터베이스의 생성이 이루어질 것입니다. 먼저 새로운 컨트롤러를 만듭니다. 그러나 그 전에 우선 프로젝트를 빌드해서 MVC 컨트롤러 스캐폴딩에서 모델 및 컨텍스트 클래스들을 사용할 수 있도록 만듭니다.

  1. 마우스 오른쪽 버튼으로 솔루션 탐색기(Solution Explorer)에서 Controllers 폴더를 클릭하고 추가(Add)를 선택한 다음, 스캐폴드 항목 새로 만들기(New Scaffolded Item)를 클릭합니다.

  2. 스캐폴드 추가(Add Scaffold) 대화 상자가 나타나면 Entity Framework를 사용하며 뷰가 포함된 MVC 5 컨트롤러(MVC 5 Controller with views, using Entity Framework)를 선택합니다.

  3. 그러면 컨트롤러 추가(Add Controller) 대화 상자가 나타나는데, 각각의 항목들을 다음과 같이 입력하고 추가(Add) 버튼을 클릭합니다:

    • 모델 클래스: Student (ContosoUniversity.Models). (만약 드롭다운 목록에 이 항목이 나타나지 않으면 프로젝트를 빌드한 다음 다시 시도해보십시오.)
    • 데이터 컨텍스트 클래스: SchoolContext (ContosoUniversity.DAL).
    • 컨트롤러 이름: StudentController (StudentsController가 아님).
    • 다른 항목들은 기본값을 그대로 남겨둡니다.

    모든 입력을 마치고 추가(Add) 버튼을 클릭하면 스캐폴더가 StudentController.cs 파일을 생성하고 이 컨트롤러와 함께 동작하는 뷰들(.cshtml 파일들)을 설정해줍니다. 나중에 Entity Framework를 사용하는 프로젝트를 생성할 때는 스캐폴더의 몇 가지 추가적인 기능들을 활용할 수도 있습니다. 예를 들어서, 연결 문자열조차 생성하지 않은 상태에서 첫 번째 모델 클래스만 생성한 다음, 컨트롤러 추가(Add Controller) 대화 상자에 새로운 컨텍스트 클래스를 지정할 수도 있습니다. 그러면 스캐폴더가 컨트롤러와 뷰 뿐만 아니라 DbContext 클래스와 연결 문자열까지 생성해줍니다.

  4. 자동으로 Visual Studio가 생성된 Controllers\StudentController.cs 파일을 열어줍니다. 그 코드를 살펴보면 데이터베이스 컨텍스트 개체의 인스턴스를 담고 있는 클래스 변수가 생성된 것을 알 수 있습니다:

    private SchoolContext db = new SchoolContext();

    그리고 Index 액션 메서드는 이 데이터베이스 컨텍스트 인스턴스의 Students 속성을 읽어서 Students 엔터티 집합으로부터 학생들의 목록을 얻습니다:

    public ViewResult Index()
    {
        return View(db.Students.ToList());
    }

    마지막으로 Student\Index.cshtml 뷰는 이렇게 가져온 목록을 테이블로 출력합니다:

    <table>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.LastName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.FirstMidName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.EnrollmentDate)
            </th>
            <th></th>
        </tr>
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
  5. 이제 CTRL+F5 키를 눌러서 프로젝트를 실행합니다. (만약 "섀도 복사할 수 없습니다."라는 오류가 발생한다면, 브라우저를 닫고 다시 시도하십시오.)

    앞에서 살펴본 Seed 메서드에 의해 삽입된 테스트 데이터를 확인하려면 Students 탭을 클릭합니다. 이 때 브라우저 창이 얼마나 넓게 열려 있는지에 따라서, 페이지 상단에 Students 탭 링크가 바로 나타나거나 우측 상단의 아이콘을 클릭해야만 링크가 나타납니다.

데이터베이스 확인하기

방금과 같이 Students 페이지를 실행하면 응용 프로그램이 데이터베이스에 접근을 시도하게 되고, Entity Framework가 데이터베이스가 존재하지 않는다는 것을 인식해서 새로운 데이터베이스를 생성한 다음, seed 메서드를 실행해서 데이터베이스에 데이터를 채워줍니다.

그리고 Visual Studio의 서버 탐색기(Server Explorer)SQL Server 개체 탐색기(SSOX, SQL Server Object Explorer)를 사용하면 데이터베이스의 데이터를 살펴볼 수 있습니다. 본 자습서에서는 서버 탐색기(Server Explorer)를 사용해보겠습니다. (2013버전 이전의 Visual Studio Express 에디션에서는 서버 탐색기(Server Explorer)데이터베이스 탐색기(Database Explorer)라고 불렀습니다.)

  1. 브라우저를 닫습니다.

  2. 서버 탐색기(Server Explorer)에서 데이터 연결(Data Connections) 노드와 그 하위의 SchoolContext (ContosoUniversity) 노드 및 테이블(Tables) 노드를 차례로 확장해보면 새로운 데이터베이스에 생성된 테이블들을 확인할 수 있습니다.

  3. 마우스 오른쪽 버튼으로 Student 테이블을 클릭한 다음 테이블 데이터 표시(Show Table Data)를 선택해서 테이블에 생성된 컬럼들과 삽입된 로우들을 살펴봅니다.

  4. 서버 탐색기(Server Explorer)의 연결을 닫습니다.

이 데이터베이스의 ContosoUniversity1.mdf  및 .ldf  파일들은 C:\Users\<yourusername> 폴더에 생성됩니다.

또한 DropCreateDatabaseIfModelChanges 이니셜라이저를 사용하고 있으므로 Student 클래스를 수정하고 응용 프로그램을 다시 실행하면 데이터베이스가 변경사항에 맞게 자동으로 재생성됩니다. 가령, Student 클래스에 EmailAddress 속성을 추가하고 다시 Students 페이지를 실행한 다음 테이블을 살펴보면 EmailAddress 컬럼이 추가된 것을 확인할 수 있을 것입니다.

규약

본문에서 Entity Framework가 자동으로 전체 데이터베이스를 생성할 수 있도록 구성하기 위해서 작성한 코드의 양이 상대적으로 매우 적은 이유는, 사용된 규약(Conventions) 과 Entity Framework가 내부적으로 갖고 있는 가정 때문입니다. 그 중 일부에 대해서는 이미 살펴봤거나 크게 의식하지 않고 자연스럽게 사용했습니다:

  • 엔터티 클래스 이름의 복수형이 테이블 이름으로 사용됩니다.
  • 엔터티의 속성 이름이 컬럼 이름으로 사용됩니다.
  • ID 또는 classnameID 형태의 이름을 갖고 있는 엔터티 속성들은 기본 키 속성으로 간주됩니다.
  • <탐색 속성 이름><기본 키 속성 이름> 형태로 구성된 이름을 갖고 있는 속성들은 왜래 키 속성으로 간주됩니다 (가령, Student 엔터티의 기본 키는 ID이므로 StudentID 속성이 Student 탐색 속성에 대한 왜래 키로 간주됩니다). 또는 간단하게 <기본 키 속성 이름> 과 동일한 왜래 키 이름을 가질 수도 있습니다. (Enrollment 엔터티의 기본 키는 EnrollmentID이므로 EnrollmentID 역시 왜래 키로 간주됩니다).

또한 이 규약들이 재정의 될 수 있다는 사실도 알아봤습니다. 가령, 본문에서는 복수형 테이블 이름을 사용하지 않도록 지정했으며, 이후의 자습서 과정에서는 명시적으로 특정 속성을 왜래 키 속성으로 설정하는 방법에 대해서도 알아볼 것입니다. 규약들에 대한 보다 자세한 내용들과 그 규약들을 재정의하는 방법에 대해서는 본 자습서 시리즈 중 Creating a More Complex Data Model 자습서에서 자세하게 살펴보게 될 것입니다. 규약에 대한 더 많은 정보들은 Code First Conventions 문서를 참고하시기 바랍니다.

요약

지금까지 데이터를 출력하거나 저장하기 위해서 Entity Framework 및 SQL Server Express LocalDB를 사용하는 간단한 응용 프로그램을 만들어 봤습니다. 다음 단계에서는 기본적인 CRUD(Create, Read, Update, Delete) 작업을 수행하는 방법을 살펴보도록 하겠습니다.

자습서 내용 중 만족스러웠던 점이나 개선사항에 대한 의견이 있으면 피드백으로 남겨주시기 바랍니다. 또한 Show Me How With Code를 통해서 새로운 주제를 요청하실 수도 있습니다.

다른 Entity Framework 리소스들에 대한 링크들은 ASP.NET Data Access - Recommended Resources 기사를 참고하시기 바랍니다.

이 기사는 2014년 4월 23일에 최초 작성되었습니다.