파트 12: MVC 5 웹 응용 프로그램과 고급 Entity Framework 6 시나리오

등록일시: 2016-07-04 08:00,  수정일시: 2016-07-04 08:46
조회수: 12,173
이 문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
이번 마지막 파트에서는 Entity Framework Code First를 이용해서 ASP.NET 웹 응용 프로그램을 개발할 때, 고급 시나리오를 처리하기 위해서 필요한 몇 가지 유용한 주제들을 살펴봅니다.

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

본 자습서의 Contoso University 예제 웹 응용 프로그램은 Entity Framework 6와 Visual Studio 2013을 이용해서 ASP.NET MVC 5 응용 프로그램을 구축하는 방법을 보여줍니다. 보다 자세한 정보는 본 자습서 시리즈의 첫 번째 자습서를 참고하시기 바랍니다.

이전 파트에서는 계층당 하나의 테이블(TPH, Table-Per-Hierarchy) 상속을 구현해봤습니다. 본문에서는 Entity Framework Code First를 이용해서 ASP.NET 웹 응용 프로그램을 개발하는 과정 중, 기초적인 수준 이상의 시나리오를 처리해야 할 때 필요한 몇 가지 유용한 주제들을 다루고 있습니다. 먼저 다음 주제들에 관해서 단계별 지시에 따라서 코드와 Visual Studio의 사용법을 살펴봅니다:

이어서 다음 몇 가지 주제들을 간략하게 살펴보고, 더 자세한 정보를 위한 링크를 제공합니다:

다음과 같은 추가 절들도 포함하고 있습니다:

위의 주제들을 살펴보면서 대부분 지금까지 본 자습서 시리즈에서 구현한 페이지들을 다시 활용합니다. 그러나 원시 SQL을 사용해서 대량의 갱신 작업을 처리하는 절에서는 데이터베이스에 존재하는 모든 강의의 학점을 갱신하는 새로운 예제 웹 페이지를 생성해봅니다:

원시 SQL 쿼리 실행하기

Entity Framework Code First API는 SQL 명령을 직접 데이터베이스로 전송하는 메서드들을 포함하고 있습니다. 이 메서드들을 상황에 따라서 다음과 같이 선택적으로 사용할 수 있습니다:

  • 엔터티 형식을 반환하는 쿼리를 실행하려면 DbSet.SqlQuery 메서드를 사용하십시오. 반환되는 개체는 반드시 DbSet 객체로 예상되는 형식이어야만 하며, 명시적으로 추적 기능을 해제하지 않는 한 데이터베이스 컨텍스트에 의해서 자동으로 추적됩니다. (이후의 AsNoTracking 메서드에 관한 절을 참고하시기 바랍니다.)
  • 엔터티 이외의 형식을 반환하는 쿼리를 실행하려면 Database.SqlQuery 메서드를 사용하십시오. 이 메서드로 반환되는 데이터는 설령 엔터티 형식을 조회하더라도 데이터베이스 컨텍스트에 의해서 추적되지 않습니다.
  • 비-쿼리 명령을 (UPDATE 문 같은) 실행하려면 Database.ExecuteSqlCommand 메서드를 사용하십시오.

Entity Framework를 사용할 때 얻을 수 있는 장점 중 하나는, 과도하게 특정 데이터 저장 방식에 최적화된 코드를 작성하지 않아도 된다는 것입니다. 이는 Entity Framework가 SQL 쿼리나 명령을 자동으로 생성해주기 때문인데, 같은 이유 때문에 직접 SQL 쿼리나 명령을 작성할 필요가 없는 장점도 있습니다. 반면 직접 작성한 특정 SQL 쿼리를 실행해야만 하는 예외적인 상황도 존재하며, 바로 그런 경우 이 메서드들을 사용할 수 있습니다.

일반적으로 웹 응용 프로그램에서 SQL 명령을 실행할 때는 항상 SQL 주입 공격(SQL Injection Attacks)으로부터 사이트를 보호하기 위한 예방 조치를 취해야합니다. 그 한 가지 방법은 웹 페이지에서 제출된 문자열을 SQL 명령으로 인식하지 않도록 보장해주는 매개변수가 있는 쿼리(Parameterized Queries)를 사용하는 것입니다. 본문에서는 사용자의 입력을 쿼리에 적용할 때, 항상 매개변수가 있는 쿼리를 사용합니다.

엔터티를 반환하는 쿼리 호출하기

DbSet<TEntity> 클래스는 TEntity 형식의 엔터티를 반환하는 쿼리를 실행할 때 사용할 수 있는 메서드를 제공해줍니다. Department 컨트롤러에서 Details 메서드의 코드를 변경해서 이 메서드가 동작하는 방법을 살펴보겠습니다.

DepartmentController.cs 파일을 열고 Details 메서드의 db.Departments.FindAsync 메서드 호출을 db.Departments.SqlQuery 메서드 호출로 대체합니다:

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    // Commenting out original code to show how to use a raw SQL query.
    //Department department = await db.Departments.FindAsync(id);

    // Create and execute raw SQL query.
    string query = "SELECT * FROM Department WHERE DepartmentID = @p0";
    Department department = await db.Departments.SqlQuery(query, id).SingleOrDefaultAsync();

    if (department == null)
    {
        return HttpNotFound();
    }
    return View(department);
}

변경된 코드가 정상적으로 동작하는지 확인해보려면, Departments 메뉴를 선택한 다음, 목록에서 특정 학과를 선택해서 Details 페이지를 확인해보면 됩니다.

다른 형식의 개체를 반환하는 쿼리 호출하기

본 자습서 시리즈의 세 번째 파트에서는 About 페이지에 각 수강일자별 학생들의 수를 보여주는 학생 통계 그리드를 만들어봤습니다. 현재 이 작업을 처리하는 HomeController.cs 의 코드는 LINQ를 사용하고 있습니다:

var data = from student in db.Students
           group student by student.EnrollmentDate into dateGroup
           select new EnrollmentDateGroup()
           {
               EnrollmentDate = dateGroup.Key,
               StudentCount = dateGroup.Count()
           };

이 데이터 조회 코드를 LINQ 대신 직접 SQL 구문으로 작성해야 한다고 가정해보십시오. 그러려면 엔터티 개체가 아닌 개체를 반환하는 쿼리를 실행해야 하므로 Database.SqlQuery 메서드를 사용해야 합니다.

다음에 강조된 코드처럼, HomeController.cs 파일에서 About 메서드의 LINQ 구문을 SQL 구문으로 대체합니다:

public ActionResult About()
{
    // Commenting out LINQ to show how to do the same thing in SQL.
    //IQueryable<EnrollmentDateGroup> = from student in db.Students
    //           group student by student.EnrollmentDate into dateGroup
    //           select new EnrollmentDateGroup()
    //           {
    //               EnrollmentDate = dateGroup.Key,
    //               StudentCount = dateGroup.Count()
    //           };

    // SQL version of the above LINQ code.
    string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
        + "FROM Person "
        + "WHERE Discriminator = 'Student' "
        + "GROUP BY EnrollmentDate";
    IEnumerable<EnrollmentDateGroup> data = db.Database.SqlQuery<EnrollmentDateGroup>(query);
	
    return View(data.ToList());
}

그런 다음, About 페이지를 실행시켜 봅니다. 그러면 기존과 동일한 데이터가 출력되는 것을 확인하실 수 있습니다.

갱신 쿼리 호출하기

이번에는 Contoso University의 관리자가 모든 강의의 학점을 일괄로 변경하는 대량의 변경 작업을 데이터베이스에서 수행하고자 한다고 가정해보겠습니다. 만약 대학에 상당 수의 강의가 개설되어 있다면, 모든 강의를 엔터티로 조회한 다음 하나 씩 개별적으로 변경하는 방식은 상당히 비효율적입니다. 이번 절에서는 관리자가 모든 강의의 학점을 변경하기 위해 사용되는 인자를 입력할 수 있는 웹 페이지를 구현한 다음, SQL UPDATE 구문을 실행해서 변경을 적용해보겠습니다. 다음 그림은 이번 절에서 만들어볼 웹 페이지의 모습을 보여줍니다:

먼저 CourseContoller.cs 파일을 열고 HttpGet 버전과 HttpPost 버전의 UpdateCourseCredits 메서드를 추가합니다:

public ActionResult UpdateCourseCredits()
{
    return View();
}

[HttpPost]
public ActionResult UpdateCourseCredits(int? multiplier)
{
    if (multiplier != null)
    {
        ViewBag.RowsAffected = db.Database.ExecuteSqlCommand("UPDATE Course SET Credits = Credits * {0}", multiplier);
    }
    return View();
}

컨트롤러가 HttpGet 요청을 처리할 때는 ViewBag.RowsAffected 변수를 통해서 전달되는 값이 없기 때문에, 처음 그림에서 본 것처럼 뷰에 빈 텍스트 상자가 출력됩니다.

그러나 Update 버튼이 클릭되면, HttpPost 메서드가 실행되고 텍스트 상자에 입력한 값이 multiplier 매개변수에 설정됩니다. 그러면 코드가 모든 강의를 갱신하는 SQL 구문을 실행한 다음, 영향 받은 로우의 수를 ViewBag.RowsAffected 변수에 담아서 뷰에 전달해줍니다. 뷰는 변수에 설정된 값을 확인하면, 다음 그림과 같이 텍스트 상자와 Update 버튼 대신 갱신된 로우들의 갯수를 출력합니다:

다시 CourseController.cs  파일에서 UpdateCourseCredits 메서드 중 하나를 마우스 오른쪽 버튼으로 클릭한 다음, 뷰 추가(Add View)를 선택해서 뷰를 추가합니다.

그리고 Views\Course\UpdateCourseCredits.cshtml  파일이 생성되면 템플릿 코드를 다음 코드로 대체합니다:

@model ContosoUniversity.Models.Course

@{
    ViewBag.Title = "UpdateCourseCredits";
}

<h2>Update Course Credits</h2>

@if (ViewBag.RowsAffected == null)
{
    using (Html.BeginForm())
    {
        <p>
            Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
        </p>
        <p>
            <input type="submit" value="Update" />
        </p>
    }
}
@if (ViewBag.RowsAffected != null)
{
    <p>
        Number of rows updated: @ViewBag.RowsAffected
    </p>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>

브라우저에서 Courses 메뉴를 선택한 다음, 주소 표시줄의 URL 끝에 "/UpdateCourseCredits"를 추가해서 UpdateCourseCredits 메서드를 실행합니다 (http://localhost:xxxx/Course/UpdateCourseCredits). 그리고 텍스트 상자에 숫자를 입력합니다:

Update 버튼을 클릭합니다. 그러면 영향받은 로우의 갯수를 확인할 수 있습니다:

이제 Back to List 링크를 클릭해서 수정된 학점들이 반영된 강의들을 목록에서 확인해봅니다.

원시 SQL 쿼리에 대한 더 많은 정보는 MSDN의 Raw SQL Queries 문서를 참고하시기 바랍니다.

비-추적 쿼리

기본적으로 데이터베이스 컨텍스트는 테이블의 로우를 조회할 때, 각 로우들에 대한 엔터티 개체들을 생성한 다음, 메모리에 생성된 엔터티들과 데이터베이스 내용 간의 동기화 여부를 추적하기 시작합니다. 메모리에 위치한 이 데이터는 일종의 캐시 역할을 수행하며 엔터티를 갱신할 때도 이 데이터가 사용됩니다. 웹 응용 프로그램에서는 이 캐시가 불필요한 경우가 종종 있는데, 일반적으로 컨텍스트 인스턴스의 수명이 짧고 (매번 요청 시마다 새로운 인스턴스가 만들어지고 삭제됩니다), 대부분 엔터티를 읽는 컨텍스트가 엔터티를 다시 사용하기 전에 제거되기 때문입니다.

메모리 상의 엔터티 개체들에 대한 추적을 해제하기 위해서는 AsNoTracking 메서드를 사용하면 됩니다. 보통 이런 작업이 필요한 시나리오로는 다음과 같은 경우들이 있습니다:

  • 대량의 데이터를 조회하는 경우처럼 추적을 해제하면 눈에 띄게 성능이 향상되는 쿼리를 호출하는 경우.
  • 갱신하기 위해서 특정 엔터티를 첨부해야 하지만, 이미 다른 용도로 동일한 엔터티를 조회한 경우. 이는 다른 데이터베이스 컨텍스트가 이미 엔터티를 추적하고 있으면, 해당 엔터티를 갱신하기 위해서 데이터베이스 컨텍스트에 첨부할 수 없기 때문입니다. 이 상황을 해결할 수 있는 한 가지 방법은 선행 쿼리에서 AsNoTracking 메서드를 사용하는 것입니다.

구체적인 AsNoTracking 메서드의 사용 예제는 본 자습서의 이전 버전을 참고하시기 바랍니다. 이번 버전의 자습서에서는 Edit 메서드에서 모델 바인더가 생성해주는 엔터티에 Modified 플래그를 설정하지 않기 때문에 AsNoTracking 메서드를 사용할 필요가 없습니다.

데이터베이스로 전송되는 쿼리 살펴보기

때로는 데이터베이스로 전송되는 실제 SQL 쿼리를 살펴보면 많은 도움이 됩니다. 이미 본 자습서 시리즈에서는 인터셉터 코드를 활용해서 로그에 SQL 쿼리를 남기는 방법을 살펴봤습니다. 이번에는 인터셉터 코드를 작성하지 않고 SQL 쿼리를 살펴볼 수 있는 다른 방법을 사용합니다. 우선 간단한 쿼리를 살펴보고, 거기에 즉시 로드(Eager Loading)와 필터링, 그리고 정렬 같은 옵션들을 추가하면 어떤 결과가 나타나는지 확인해보겠습니다.

먼저 Controllers/CourseController  파일을 열고 Index 메서드를 다음 코드로 대체해서 즉시 로드를 잠시 중단시킵니다:

public ActionResult Index()
{
    var courses = db.Courses;
    var sql = courses.ToString();
    return View(courses.ToList());
}

그리고 return 구문에 중단점을 설정합니다 (이 줄에 커서를 위치시키고 F9 키를 누릅니다). 그런 다음, F5 키를 눌러서 디버거 모드로 프로젝트를 실행하고 Course Index 페이지로 이동합니다. 코드가 중단점에 도달하면 sql 변수에 마우스 커서를 올려서 쿼리를 살펴보시기 바랍니다. 그러면 SQL Server로 전송되는 쿼리를 직접 확인할 수 있는데, 지금은 간단한 Select 구문임을 알 수 있습니다.

{SELECT
    [Extent1].[CourseID] AS [CourseID], 
    [Extent1].[Title] AS [Title], 
    [Extent1].[Credits] AS [Credits], 
    [Extent1].[DepartmentID] AS [DepartmentID]
    FROM [Course] AS [Extent1]}

돋보기 아이콘을 클릭하면 텍스트 시각화 도우미(Text Visualizer)에서 편리하게 쿼리를 살펴볼 수도 있습니다.

계속해서 사용자가 특정 학과를 선택해서 필터를 설정할 수 있도록 Courses Index 페이지에 드롭다운 목록을 추가해보겠습니다. 또한 이름순으로 강의를 정렬하고, Department 탐색 속성에 대한 즉시 로드도 같이 지정할 것입니다.

다시 CourseController.csIndex 메서드 코드를 다음 코드로 대체합니다:

public ActionResult Index(int? SelectedDepartment)
{
    var departments = db.Departments.OrderBy(q => q.Name).ToList();
    ViewBag.SelectedDepartment = new SelectList(departments, "DepartmentID", "Name", SelectedDepartment);
    int departmentID = SelectedDepartment.GetValueOrDefault();

    IQueryable<Course> courses = db.Courses
        .Where(c => !SelectedDepartment.HasValue || c.DepartmentID == departmentID)
        .OrderBy(d => d.CourseID)
        .Include(d => d.Department);
    var sql = courses.ToString();
    return View(courses.ToList());
}

그리고 return 구문에 다시 중단점을 설정합니다.

이 메서드는 사용자가 드롭다운 목록에서 선택한 값을 SelectedDepartment 매개변수를 통해서 전달받습니다. 만약 선택된 학과가 없다면 이 매개변수에는 null이 설정됩니다.

모든 학과들의 목록을 담고 있는 SelectList 컬렉션은 뷰에 전달되어 드롭다운 목록의 생성에 사용됩니다. SelectList 생성자에 전달되는 매개변수들은 값 필드의 이름과 텍스트 필드의 이름, 그리고 선택된 항목을 지정합니다.

이 코드는 Course 엔터티 목록을 대상으로 필터 표현식과 정렬 순서, 그리고 Department 탐색 속성에 관한 즉시 로드를 지정하고 있습니다. 필터 표현식은 드롭다운 목록에서 선택된 학과가 없으면 (SelectedDepartment 매개변수가 null이면) 모든 레코드에 대해서 true를 반환합니다.

이번에는 Views\Course\Index.cshtml  파일을 열고, 다음 코드를 여는 table 태그 직전에 추가해서 드롭다운 목록과 Filter 버튼을 추가합니다:

@using (Html.BeginForm())
{
    <p>Select Department: @Html.DropDownList("SelectedDepartment","All")
    <input type="submit" value="Filter" /></p>
}

중단점을 설정한 상태로 다시 Course Index 페이지를 실행합니다. 일단 처음에는 중단점에 도달하더라도 브라우저가 페이지를 출력하도록 중단점을 무시하고 계속 실행합니다. 그런 다음, 드롭다운 목록에서 학과를 선택하고 Filter 버튼을 클릭합니다:

이번에는 중단점에 도달하면 어떤 Course 쿼리가 전송되는지 sql 변수를 통해서 확인해봅니다. 그러면 다음과 비슷한 쿼리를 볼 수 있을 것입니다:

SELECT
    [Project1].[CourseID] AS [CourseID], 
    [Project1].[Title] AS [Title], 
    [Project1].[Credits] AS [Credits], 
    [Project1].[DepartmentID] AS [DepartmentID], 
    [Project1].[DepartmentID1] AS [DepartmentID1], 
    [Project1].[Name] AS [Name], 
    [Project1].[Budget] AS [Budget], 
    [Project1].[StartDate] AS [StartDate], 
    [Project1].[InstructorID] AS [InstructorID], 
    [Project1].[RowVersion] AS [RowVersion]
    FROM ( SELECT 
        [Extent1].[CourseID] AS [CourseID], 
        [Extent1].[Title] AS [Title], 
        [Extent1].[Credits] AS [Credits], 
        [Extent1].[DepartmentID] AS [DepartmentID], 
        [Extent2].[DepartmentID] AS [DepartmentID1], 
        [Extent2].[Name] AS [Name], 
        [Extent2].[Budget] AS [Budget], 
        [Extent2].[StartDate] AS [StartDate], 
        [Extent2].[InstructorID] AS [InstructorID], 
        [Extent2].[RowVersion] AS [RowVersion]
        FROM  [dbo].[Course] AS [Extent1]
        INNER JOIN [dbo].[Department] AS [Extent2] ON [Extent1].[DepartmentID] = [Extent2].[DepartmentID]
        WHERE @p__linq__0 IS NULL OR [Extent1].[DepartmentID] = @p__linq__1
    )  AS [Project1]
    ORDER BY [Project1].[CourseID] ASC

이번에는 Course 데이터와 Department 데이터를 함께 로드하는 JOIN 형태의 쿼리가 전송되고 있으며, WHERE 절을 포함하고 있음을 확인할 수 있습니다.

마지막으로 var sql = courses.ToString() 줄을 제거합니다.

리파지터리 및 작업 단위 패턴

많은 개발자들이 Entity Framework 관련 작업 코드를 감싸는 래퍼의 용도로 리파지터리 및 작업 단위 패턴(Repository and Unit of Work Patterns)을 구현한 코드를 작성합니다. 이런 패턴은 응용 프로그램의 데이터 접근 계층과 업무 로직 계층 간에 추상화 계층을 제공하는 것이 그 목표입니다. 이를 구현하면 데이터 저장소가 변경되더라도 응용 프로그램에 미치는 영향을 최소화할 수 있으며, 자동화된 단위 테스트나 테스트 주도 개발(TDD, Test-Driven Development)이 더 용이합니다. 그러나 다음과 같은 몇 가지 이유에서, EF를 사용하는 응용 프로그램에 추가적인 코드를 작성해서 이런 패턴을 구현하는 것이 언제나 최선의 선택인 것 만은 아닙니다:

  • EF 컨텍스트 클래스 자체가 여러분의 코드와 데이터 저장 관련(Data-Store-Specific) 코드를 격리시켜줍니다.
  • EF 컨텍스트 클래스가 EF로 수행하는 데이터베이스 갱신에 대한 작업 단위(Unit-of-Work) 클래스의 역할을 수행할 수 있습니다.
  • Entity Framework 6에 도입된 기능들을 활용하면 리파지터리 코드를 작성하지 않고도 손쉽게 TDD를 구현할 수 있습니다.

리파지터리 및 작업 단위 패턴의 구현방법에 대한 보다 자세한 정보는 본 자습서 시리즈의 Entity Framework 5 버전을 참고하시기 바랍니다. 그리고 Entity Framework 6에 대한 TDD 구현 방법에 대한 자세한 내용은 다음 자료들을 참고하시기 바랍니다:

프록시 클래스

Entity Framework는 종종 엔터티의 인스턴스를 생성할 때 (이를테면, 쿼리를 실행할 때), 이를 해당 엔터티에 대한 프록시 역할을 수행하는 동적으로 생성되는 파생 형식의 인스턴스로 생성합니다. 다음 두 디버거 그림을 살펴보시기 바랍니다. 먼저 첫 번째 그림은 엔터티의 인스턴스를 생성한 직후, 예상했던 것처럼 Student 형식으로 생성된 student 변수를 보여줍니다. 그러나 두 번째 그림에서는 EF가 데이터베이스에서 엔터티를 조회하기 위해서 student 변수를 사용하고 난 뒤, 그 형식이 프록시 클래스로 변경되는 것을 확인할 수 있습니다.

프록시 클래스가 일부 가상 속성들을 재정의하는 이유는, 후크를 삽입함으로써 해당 속성에 접근할 때 자동으로 특정 작업을 수행하기 위한 것입니다. 가령, 이 메커니즘이 사용되는 기능 중 하나가 바로 지연 로드입니다.

대부분 이렇게 프록시가 사용된다는 사실 자체도 의식할 필요가 없지만, 몇 가지 예외적인 상황이 존재합니다:

  • 특정 시나리오에서는 Entity Framework가 프록시 인스턴스를 생성하는 것을 막아야만 합니다. 가령, 엔터티를 직렬화하려는 경우, 일반적으로 POCO 클래스를 직렬화하고자 하는 것이지, 프록시 클래스를 직렬화하려는 것이 아니기 때문입니다. 직렬화 문제를 피할 수 있는 방법 중 한 가지는 Using Web API with Entity Framework 자습서에서 설명하고 있는 것처럼 엔터티 개체 대신 데이터 전송 개체(DTOs, Data Transfer Objects)를 직렬화 하는 것입니다. 또는 프록시 생성을 비활성화시키는 방법도 있습니다.
  • new 연산자를 사용해서 엔터티 클래스의 인스턴스를 생성하는 경우에는 프록시 인스턴스가 생성되지 않습니다. 결국 이 얘기는 지연 로드나 변경내용 자동 추적 같은 기능들을 활용할 수 없다는 뜻이기도 합니다. 그러나 일반적으로 이 점이 문제가 되지는 않는데, 데이터베이스에 존재하지 않는 엔터티의 인스턴스를 생성하는 작업이기 때문에 지연 로드할 필요가 없으며, 명시적으로 엔터티를 Added 상태로 표시할 경우에는 대부분 변경 추적 기능도 필요 없기 때문입니다. 결론적으로 지연 로드나 변경 추적 등의 기능이 필요하다면 DbSet 클래스의 Create 메서드를 이용해서 프록시와 함께 엔터티의 새로운 인스턴스를 생성해야 합니다.
  • 필요한 경우, 프록시 형식에서 실제 엔터티 형식을 가져올 수도 있습니다. 프록시 형식의 인스턴스에서 실제 엔터티 형식을 얻으려면 ObjectContext 클래스의 GetObjectType 메서드를 사용하면 됩니다.

더 자세한 정보는 MSDN의 Working with Proxies 문서를 참고하시기 바랍니다.

자동 변경 감지

Entity Framework는 엔터티의 현재 값과 원본 값을 비교해서 엔터티가 어떻게 변경됐는지 (그에 따라 데이터베이스를 어떻게 변경해야 하는지) 확인합니다. 원본 값은 엔터티를 질의하거나 첨부할 때 저장되는데, 다음 같은 메서드들을 사용하면 자동 변경 감지가 시작됩니다:

  • DbSet.Find
  • DbSet.Local
  • DbSet.Remove
  • DbSet.Add
  • DbSet.Attach
  • DbContext.SaveChanges
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries

만약 대량의 엔터티들을 추적하면서 루프 내부에서 이 메서드들 중 하나를 여러 번 호출한다면, AutoDetectChangesEnabled 속성을 이용해서 자동 변경 감지를 일시적으로 해제하면 상당한 성능 향상을 얻을 수 있습니다. 더 자세한 정보는 MSDN의 Automatically Detecting Changes 문서를 참고하시기 바랍니다.

자동 유효성 검사

기본적으로 Entity Framework는 SaveChanges 메서드가 호출되어 데이터베이스를 갱신하기 전에, 변경된 모든 엔터티들의 모든 속성에 저장된 데이터의 유효성 검사를 수행합니다. 만약 대량의 엔터티들을 갱신해야 하는 상황에서 이미 데이터의 유효성 검사를 마쳤다면 이 과정은 불필요하기 때문에, 유효성 검사를 일시적으로 해제하기만 해도 변경사항을 저장하는데 걸리는 시간을 절약할 수 있습니다. 이 설정은 ValidateOnSaveEnabled 속성을 이용해서 처리할 수 있습니다. 더 자세한 정보는 MSDN의 Validation 문서를 참고하시기 바랍니다.

Entity Framework Power Tools

Entity Framework Power Tools은 본 자습서 시리즈에서 데이터 모델 다이어그램을 생성할 때 사용한 Visual Studio의 추가 기능입니다. 이 도구는 기존 데이터베이스의 테이블을 기반으로 엔터티 클래스들을 생성하여 Code First를 사용할 수 있도록 해주는 등과 같은 다른 다양한 기능들도 함께 제공해줍니다. 이 도구를 설치하고 나면 컨텍스트 메뉴에 몇 가지 추가적인 항목들이 나타납니다. 예를 들어서, 솔루션 탐색기(Solution Explorer)에서 컨텍스트 클래스를 마우스 오른쪽 버튼으로 클릭하면 다이어그램을 생성할 수 있는 메뉴가 나타날 것입니다. Code First를 사용하면 다이어그램을 이용해서 데이터 모델을 변경할 수는 없지만, 이해를 돕기 위해서 배치를 변경할 수는 있습니다.

Entity Framework 소스 코드

Entity Framework 6의 소스 코드는 http://entityframework.codeplex.com/에서 살펴보실 수 있습니다. 이 CodePlex 페이지에서는 소스 코드 뿐만 아니라, 일단위 자동 빌드(Nightly Builds), 이슈 추적, 기능 사양, 설계 회의록 등을 얻고 살펴보실 수 있습니다. 버그를 보고하거나 여러분이 직접 개선한 코드를 EF 소스 코드에 기여할 수도 있습니다.

비록 소스 코드는 오픈되어 있지만, Entity Framework는 Microsoft의 제품으로 완벽하게 지원됩니다. Microsoft의 Entity Framework 팀이 지속적으로 기여 받은 코드의 승인을 관리하고, 각 릴리즈의 품질을 보장하기 위해서 모든 코드 변경사항들을 테스트하고 있습니다.

요약

이것으로 ASP.NET MVC 응용 프로그램에서 Entity Framework Code First를 사용하는 방법에 관한 본 자습서 시리즈를 마무리하도록 하겠습니다. Entity Framework로 데이터를 처리하는 다양한 방법에 대한 보다 자세한 정보는, MSDN의 EF Documentation 페이지와 ASP.NET Data Access - Recommended Resources 기사를 참고하시기 바랍니다.

구축을 마친 웹 응용 프로그램의 배포 방법에 대한 정보는 ASP.NET Web Deployment - Recommended Resources 기사를 참고하시면 됩니다.

마지막으로 인증이나 권한 등, 다양한 MVC의 다른 주제들에 대한 정보는 ASP.NET MVC - Recommended Resources 기사를 참고하시기 바랍니다.

감사의 글

  • Tom Dykstra는 본 자습서의 최초 버전을 집필했으며, EF 5 업데이트의 공동 집필자이자 EF 6 업데이트의 집필자입니다. Tom은 Microsoft Web Platform and Tools Content Team의 수석 프로그래밍 저자입니다.
  • Rick Anderson (twitter @RickAndMSFT)은 EF 5 및 MVC 4에 관한 자습서의 대부분을 집필했으며, EF 6 업데이트의 공동 집필자입니다. Rick은 Microsoft의 Azure와 MVC에 관심이 많은 수석 프로그래밍 저자입니다.
  • Rowan Miller를 비롯한 Entity Framework 팀의 팀원들은 코드 리뷰를 지원해주고, EF 5 및 EF 6의 자습서 시리즈를 업데이트 하는 동안 마이그레이션과 관련해서 발생한 많은 문제들의 디버그를 도와줬습니다.

VB

본 자습서 시리즈는 최초에 EF 4.1을 대상으로 작성되었으며, 당시에는 C# 버전과 VB 버전으로 다운로드 가능한 전체 프로젝트를 제공했었습니다. 그러나 시간적인 한계와 우선 순위 때문에 이번 버전에서는 VB 버전을 제공하지 못했습니다. 만약 여러분이 본 자습서의 VB 프로젝트를 구현하고 다른 사람들과 공유할 의향이 있다면 알려주시기 바랍니다.

일반적인 오류와 해결방법

Cannot create/shadow copy

오류 메시지:

Cannot create/shadow copy '<filename>' when that file already exists.

해결방법

몇 초 기다렸다가 페이지를 새로 고침합니다.

Update-Database not recognized

오류 메시지 (패키지 관리자 콘솔에서 Update-Database 명령을 실행할 때 발생합니다):

The term 'Update-Database' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

해결방법

Visual Studio를 종료합니다. 그런 다음, 프로젝트를 열고 다시 작업을 시도합니다.

Validation failed

오류 메시지 (패키지 관리자 콘솔에서 Update-Database 명령을 실행할 때 발생합니다):

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.

해결방법

이 문제가 발생하는 원인 중 하나는 Seed 메서드가 실행될 때 발생하는 유효성 검사 오류 때문입니다. Seeding and Debugging Entity Framework (EF) DBs 문서의 팁을 참고하여 Seed 메서드를 디버깅 해보시기 바랍니다.

HTTP 500.19 error

오류 메시지:

HTTP Error 500.19 - Internal Server Error
The requested page cannot be accessed because the related configuration data for the page is invalid.

해결방법

이 오류가 발생하는 원인 중 하나는 동시에 같은 포트 번호로 한 솔루션의 여러 복사본을 사용하기 때문입니다. 실행 중인 Visual Studio의 모든 인스턴스를 종료하고 작업 대상 프로젝트를 다시 시작하면 대부분 문제가 해결됩니다. 그래도 문제가 해결되지 않는다면 포트 번호를 변경해보시기 바랍니다. 솔루션 탐색기(Solution Explorer)에서 마우스 오른쪽 버튼으로 프로젝트를 클릭한 다음, 속성(Properties)을 클릭합니다. 그리고 웹(Web) 탭을 선택하고 프로젝트 URL(Project Url) 텍스트 상자에서 포트 번호를 변경하면 됩니다.

Error locating SQL Server instance

오류 메시지:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

해결방법

연결 문자열을 확인하십시오. 만약 수작업으로 데이터베이스를 삭제했다면 연결 문자열의 데이터베이스 이름을 변경하십시오.

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