파트 7: Movie 모델과 테이블에 새로운 필드 추가하기

등록일시: 2012-07-03 10:49,  수정일시: 2013-09-13 18:07
조회수: 4,408
이 문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

이번에는 모델 클래스를 일부 변경한 다음, 그 결과를 데이터베이스 스키마에 반영하는 방법을 살펴보도록 하겠습니다.

Movie 모델에 Rating 속성 추가하기

먼저, 기존 Movie 클래스에 새로운 Rating 속성을 추가하겠습니다. Models\Movie.cs 파일을 연 다음, 다음과 같은 Rating 속성을 추가합니다:

public string Rating { get; set; }

이 작업을 마치고 나면 Movie 클래스의 모습은 다음과 비슷할 것입니다:

public class Movie
{
    public int      ID          { get; set; }
    public string   Title       { get; set; }
    public DateTime ReleaseDate { get; set; }
    public string   Genre       { get; set; }
    public decimal  Price       { get; set; }
    public string   Rating      { get; set; }
}

그런 다음, Build > Build Movie 메뉴를 선택해서 응용 프로그램을 다시 컴파일합니다.

이제, Model 클래스를 변경했으므로 \Views\Movies\Index.cshtml 뷰 템플릿과 \Views\Movies\Create.cshtml 뷰 템플릿을 변경해서 추가된 Rating 속성을 브라우저에 출력해야 합니다.

먼저, \Views\Movies\Index.cshtml 파일을 열고, Price 컬럼 헤더 뒷 부분에 <th>Rating</th> 컬럼 헤더를 추가합니다. 그리고, @item.Rating 값을 렌더할 <td> 컬럼을 템플릿 끝 부분에 추가합니다. 다음은 변경을 마친 Index.cshtml 뷰 템플릿의 모습을 보여주고 있습니다:

@model IEnumerable<MvcMovie.Models.Movie> 
 
@{ 
    ViewBag.Title = "Index"; 
} 
 
<h2>Index</h2> 
 
<p> 
    @Html.ActionLink("Create New", "Create") 
</p> 
<table> 
    <tr> 
        <th> 
            @Html.DisplayNameFor(model => model.Title) 
        </th> 
        <th> 
            @Html.DisplayNameFor(model => model.ReleaseDate) 
        </th> 
        <th> 
            @Html.DisplayNameFor(model => model.Genre) 
        </th> 
        <th> 
            @Html.DisplayNameFor(model => model.Price) 
        </th> 
        <th> 
            @Html.DisplayNameFor(model => model.Rating) 
        </th> 
        <th></th> 
    </tr> 

@foreach (var item in Model) { 
    <tr> 
        <td> 
            @Html.DisplayFor(modelItem => item.Title) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.ReleaseDate) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.Genre) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.Price) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.Rating)
        </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> 
} 

</table>

이번에는 \Views\Movies\Create.cshtml 파일을 열고, 폼의 마지막 부분에 다음과 같은 마크업을 추가합니다. 이 마크업은 텍스트 박스를 렌더해주는데, 새로운 영화 정보를 생성할 때, 이 텍스트 박스를 사용해서 등급을 입력할 수 있습니다.

<div class="editor-label"> 
    @Html.LabelFor(model => model.Rating) 
</div> 
<div class="editor-field"> 
    @Html.EditorFor(model => model.Rating) 
    @Html.ValidationMessageFor(model => model.Rating) 
</div>

모델과 데이터베이스 스키마 간의 차이점 관리하기

지금까지 새로운 Rating 속성 지원을 위해서 응용 프로그램의 코드를 수정했습니다.

다시 응용 프로그램을 실행한 다음, /Movies URL로 이동해보겠습니다. 그러면, 다음과 같은 두 가지 오류 중 한 가지가 발생하는 것을 보게 될 것입니다:

이런 오류들이 발생하는 이유는, 지금 막 변경한 응용 프로그램의 Movie 모델 클래스와 데이터베이스의 Movie 테이블 스키마가 서로 일치하지 않기 때문입니다. (데이터베이스의 테이블에는 아직 Rating 속성에 해당하는 컬럼이 존재하지 않습니다.)

이전 강좌에서 살펴본 것처럼, Entity Framework Code First를 이용해서 데이터베이스를 자동생성하면, 생성된 데이터베이스의 스키마와 그에 대응하는 모델 클래스 사이에 동기화가 필요한지 여부를 지속적으로 판단하기 위한 테이블이 데이터베이스에 기본적으로 함께 추가됩니다. 그리고, 만약 동기화가 되어 있지 않은 것이 발견되면, 지금처럼 Entity Framework가 오류를 발생시키게 됩니다. 결과적으로 뒤늦게 런타임에 가서야 발견되었을 지도 모를 (모호한 오류로 인한) 문제점을 개발 시점에 손쉽게 추적할 수 있는 것입니다. 바로, 이런 동기화 점검(Sync-Checking) 기능으로 인해서 방금 살펴본 오류 메시지가 출력된 것입니다.

이 오류를 해결하기 위한 접근방법은 두 가지가 존재합니다:

  1. Entity Framework가 자동으로 데이터베이스를 드랍시킨 다음, 새로운 모델 클래스 스키마를 기준으로 데이터베이스를 재생성하도록 만드는 방법입니다. 이 방법은 모델과 데이터베이스를 신속하게 변경할 수 있으므로, 테스트 데이터베이스에서 개발이 한창 진행 중일 때 매우 편리합니다. 그러나, 단점은 데이터베이스에 존재하던 기존 데이터들이 사라지게 되므로, 절대로 운영 데이터베이스에서는 사용하면 안된다는 것입니다!
  2. 명시적으로 직접 데이터베이스의 스키마를 변경하여 모델 클래스와 일치시키는 방법입니다. 이 방법의 장점은 기존 데이터를 유지할 수 있다는 점입니다. 직접 데이터베이스를 변경하거나, 데이터베이스 변경 스크립트를 작성해서 수행할 수 있습니다.

본 자습서에서는 첫 번째 방법을 사용해서, 모델 클래스가 변경될 때마다 Entity Framework Code First가 자동으로 데이터베이스를 재생성하도록 만들어 보겠습니다.

변경된 모델에 따라 자동으로 데이터베이스 재생성하기

그러면 지금부터 응용 프로그램의 모델이 변경될 때마다, Code First가 자동으로 데이터베이스를 드랍시키고 재생성할 수 있도록 응용 프로그램을 변경해보겠습니다.

경고 데이터베이스를 자동으로 드랍시키고 재생성하는 이 방법은, 개발 데이터베이스나 테스트 데이터베이스를 사용할 때만 사용해야하며, 실제 데이터를 담고 있는 운영 데이터베이스를 대상으로는 절대 사용하시면 안됩니다. 운영 서버에 이 방식을 적용하면 데이터를 잃어버리게 될 수도 있습니다.

디버거를 중지하고, Solution Explorer에서 Models 폴더를 마우스 오른쪽 버튼으로 클릭한 다음, Add를 클릭하고, 다시 New Item을 선택합니다.

잠시 후 Add New Item 대화 상자가 나타나면, Class를 선택한 다음, 클래스의 이름을 "MovieInitializer"로 지정합니다. 그리고, 다음의 코드로 MovieInitializer 클래스의 내용을 대체합니다:

using System;
using System.Collections.Generic;
using System.Data.Entity;
 
namespace MvcMovie.Models {
    public class MovieInitializer : DropCreateDatabaseIfModelChanges<MovieDBContext> {
        protected override void Seed(MovieDBContext context) {
            var movies = new List<Movie> {

                new Movie { Title = "When Harry Met Sally",
                            ReleaseDate=DateTime.Parse("1989-1-11"),
                            Genre="Romantic Comedy",
                            Rating="R",
                            Price=7.99M },

                new Movie { Title = "Ghostbusters",
                            ReleaseDate=DateTime.Parse("1984-3-13"),
                            Genre="Comedy",
                            Rating="R",
                            Price=8.99M },

                new Movie { Title = "Ghostbusters 2",
                            ReleaseDate=DateTime.Parse("1986-2-23"),
                            Genre="Comedy",
                            Rating="R",
                            Price=9.99M },

                new Movie { Title = "Rio Bravo",
                            ReleaseDate=DateTime.Parse("1959-4-15"),
                            Genre="Western",
                            Rating="R",
                            Price=3.99M },
            }; 

            movies.ForEach(d => context.Movies.Add(d));
        }
    }
}

MovieInitializer 클래스는 모델 클래스가 변경되면, 모델이 사용하는 데이터베이스를 자동으로 드랍시키고 재생성하도록 지시합니다. 이 코드에서처럼 DropCreateDatabaseIfModelChanges 이니셜라이져를 상속 받으면 스키마가 변경된 경우에만 데이터베이스가 재생성됩니다. 또는, DropCreateDatabaseAlways 이니셜라이저를 상속 받아서, 응용 프로그램 도메인에서 컨텍스트가 처음 사용될 때마다 항상 데이터베이스를 다시 생성하고 데이터를 초기화시킬 수도 있습니다. DropCreateDatabaseAlways 이니셜라이저 방식은 특정 통합 테스트 시나리오에서 아주 유용합니다. 그리고, 이 MovieInitializer 클래스에 작성된 Seed 메서드는, 데이터베이스가 생성(또는 재생성) 될 때마다 자동으로 데이터베이스에 추가될 약간의 기본 데이터들을 지정합니다. 이 메서드를 작성해 놓으면, 모델이 변경될 때마다 매번 수작업으로 데이터를 입력할 필요 없이 원하는 테스트 데이터로 편리하게 데이터베이스를 채울 수 있습니다.

클래스 정의를 마쳤으므로, 이제 매번 응용 프로그램이 실행될 때마다 모델 클래스와 데이터베이스의 스키마가 다른지 검토할 수 있도록 MovieInitializer 클래스를 구성할 차례입니다. 검토를 해 본 결과, 동기화가 필요한 상태라면 이니셜라이저를 실행해서 데이터베이스를 모델과 일치하도록 재생성하고, 기본 데이터를 입력하게 됩니다.

먼저, Global.asax 파일을 엽니다:

Global.asax 파일에는 프로젝트의 응용 프로그램 전체를 정의하는 클래스 정의와, 응용 프로그램이 처음 시작될 때 실행되는 Application_Start 이벤트 핸들러가 들어 있습니다.

다음과 같이 Application_Start 메서드의 시작 부분에 Database.SetInitializer 호출 코드를 추가합니다:

protected void Application_Start()
{
    Database.SetInitializer<MovieDBContext>(new MovieInitializer());
    AreaRegistration.RegisterAllAreas();

    // Use LocalDB for Entity Framework by default
    Database.DefaultConnectionFactory = new SqlConnectionFactory("Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True");

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    BundleTable.Bundles.RegisterTemplateBundles();
}

그리고, 빨간색 밑줄이 쳐진 부분을 (MovieDBContextMovieInitializer) 마우스 오른쪽 버튼으로 클릭한 다음, Resolve > using MvcMovie.Models; 선택합니다.

또는, 파일 상단에 직접 다음과 같은 using 문을 추가해도 됩니다. 다만 이 using 문은 MovieInitializer 클래스가 실제로 위치한 네임스페이스를 참조해야 합니다:

using MvcMovie.Models;    // MovieInitializer

이렇게 구성을 해놓으면, 응용 프로그램이 시작될 때, 지금 막 추가한 Database.SetInitializer 구문으로 인해서 MovieDBContext 인스턴스와 관련된 데이터베이스의 스키마와 모델 클래스가 일치하지 않는 경우, 자동으로 데이터베이스가 삭제되었다가 재생성되게 됩니다. 그리고, 앞에서 설명한 것처럼 그 과정 중에 데이터베이스가 MovieInitializer 클래스에 지정된 기본 데이터로 채워지게 됩니다.

이제 Global.asax 파일을 닫습니다.

응용 프로그램을 다시 시작한 다음, /Movies URL로 이동해봅니다. 그러면, 응용 프로그램이 시작될 때, 모델의 구조와 데이터베이스 스키마가 더 이상 일치하지 않는다는 사실이 감지될 것입니다. 그에 따라, 새로운 모델 구조와 일치하도록 데이터베이스가 재생성되고 데이터베이스가 기본 영화 데이터로 채워지게 됩니다:

역주: 만약, 본문에서 설명한 대로 동작하지 않고 계속해서 오류가 발생한다면, 화면 우측 하단의 작업 표시줄에 나타나 있는 IIS Express 아이콘을 마우스 오른쪽 버튼으로 클릭한 다음, MvcMovie > 사이트 중지를 선택해서 사이트 자체를 다시 시작합니다. Global.asax 파일에 대한 변경은 웹 응용 프로그램 자체를 다시 시작해야 반영되는 경우가 많습니다.

이번에는 Create New 링크를 클릭해서 새로운 영화 정보를 추가해보십시요. 이제는 등급도 지정할 수 있다는 점에 주목하시기 바랍니다.

7_CreateRioII

적절한 정보들을 입력한 다음, Create를 클릭하면, 등급 정보가 포함된 새로운 영화 정보가 목록에 나타나는 것을 확인할 수 있을 것입니다:

7_ourNewMovie_SM

마지막으로 Edit 뷰 템플릿에도 Rating 필드를 추가해줍니다.

지금까지 모델 개체를 변경하는 방법과, 모델의 해당 변경 사항을 데이터베이스에 반영하는 방법을 살펴봤습니다. 그리고, 특정 시나리오에서 새로 생성된 데이터베이스에 기본 데이터를 채워 넣을 수 있는 방법도 살펴봤습니다. 계속해서 모델 클래스에 다채로운 유효성 검사 로직을 추가하는 방법과 특정 업무 로직을 강제하는 방법을 살펴보도록 하겠습니다.