파트 3: Entity Framework를 이용해서 정렬, 필터링, 그리고 페이징 구현하기

등록일시: 2015-12-21 08:00,  수정일시: 2016-09-02 09:12
조회수: 10,708
이 문서는 ASP.NET MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
이번 파트에서는 Entity Framework를 이용해서 간단한 정렬과 필터링, 그리고 페이징 기능을 구현해봅니다.

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

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

이전 자습서에서는 Student 엔터티를 대상으로 기본적인 CRUD 작업을 수행하는 몇 가지 웹 페이지들을 구현해봤습니다. 계속해서 이번에는 Students Index 페이지에 정렬 기능과 필터링 기능, 그리고 페이징 기능을 추가해보려고 합니다. 더불어 간단한 그룹핑을 수행하는 페이지도 만들어 보겠습니다.

다음은 본문에서 설명하게 될 작업들을 마치고 나면 만들어지게 될 페이지의 모습을 보여줍니다. 각 컬럼의 머리글은 사용자가 클릭할 때마다 해당 컬럼으로 정렬할 수 있는 하이퍼링크로 변경되어 제공됩니다. 컬럼의 머리글을 반복적으로 클릭하면 클릭할 때마다 정렬 순서가 오름차순과 내림차순을 번갈아가며 적용됩니다.

Students Index 페이지에 컬럼 정렬 하이퍼링크 추가하기

정렬 기능을 Student Index 페이지에 추가하기 위해서는, Student 컨트롤러의 Index 메서드를 수정하고 Student Index 뷰에 코드를 추가해야 합니다.

Index 메서드에 정렬 기능 추가하기

먼저 Controllers\StudentController.cs  파일을 열고 Index 메서드를 다음 코드로 대체합니다:

public ActionResult Index(string sortOrder)
{
    ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in db.Students
                   select s;
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(students.ToList());
}

이 코드는 URL의 쿼리 문자열에서 가져온 값을 sortOrder 매개변수를 통해서 전달받고 있는데, 이 값은 ASP.NET MVC가 자동으로 액션 메서드에 매개변수로 제공해줍니다. 이 매개변수는 "Name"이나 "Date" 중 한 가지 문자열 값을 갖게 되는데, 내림차순 정렬을 지정하는 경우에는 선택적으로 밑줄과 "desc"라는 문자열이 그 뒤에 따라붙게 됩니다. 본문의 예제에서 기본 정렬 순서는 오름차순입니다.

가장 처음 Index 페이지가 요청됐을 때는 쿼리 문자열이 존재하지 않습니다. 따라서 학생들의 목록은 switch 문에서 일치하는 case 문이 존재하지 않을 때 실행되도록 정의된 기본 동작에 따라 LastName을 기준으로 오름차순으로 정렬되어 출력됩니다. 그러나 사용자가 컬럼 머리글의 하이퍼링크를 클릭한 경우에는 적절한 sortOrder 값이 쿼리 문자열로 제공됩니다.

그리고 뷰에서 적절한 쿼리 문자열 값을 이용해서 컬럼 머리글의 하이퍼링크들을 구성할 수 있도록, 다음과 같은 두 가지 ViewBag 변수들이 사용되고 있습니다:

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

이 두 줄의 코드 구문에서는 삼항 연산자가 사용되고 있는데, 이를테면 첫 번째 줄은 sortOrder 매개변수의 값이 null이거나 빈 문자열이면 ViewBag.NameSortParm 변수를 "name_desc"로 설정하고, 아니면 빈 문자열로 설정합니다. 그러면 뷰에서는 이 두 변수를 이용해서 다음과 같이 컬럼 머리글의 하이퍼링크들을 설정하게 됩니다:

현재 정렬 순서 Last Name 하이퍼링크 Date 하이퍼링크
Last Name 오름차순 내림차순 오름차순
Last Name 내림차순 오름차순 오름차순
Date 오름차순 오름차순 내림차순
Date 내림차순 오름차순 오름차순

이 메서드에서는 LINQ to Entities를 이용해서 정렬할 기준 컬럼을 지정하고 있습니다. 먼저 switch 문에 진입하기 전에 IQueryable 변수를 생성한 다음, switch 문에서 조건에 따라 이를 변경하고, switch 문을 벗어난 다음에 ToList 메서드를 호출합니다. 여기서 주목해야 할 점은 IQueryable 변수를 생성하거나 변경할 때는 데이터베이스로 쿼리가 전송되지 않는다는 점입니다. 다시 말해서 ToList 등의 메서드를 호출해서 IQueryable 개체를 컬렉션으로 변환하기 전까지는 쿼리가 실행되지 않는다는 뜻입니다. 따라서 이 코드에서는 단일 쿼리만 실행되며 이 쿼리는 return View 구문이 호출되기 전까지는 실행되지 않습니다.

다른 방법으로 각각의 정렬 순서마다 다른 LINQ 문을 작성하는 대신 LINQ 구문을 동적으로 생성할 수도 있습니다. 동적 LINQ에 대한 더 자세한 정보는 Dynamic LINQ 포스트를 참고하시기 바랍니다.

Student Index 뷰에 컬럼 머리글 하이퍼링크 추가하기

이번에는 Views\Student\Index.cshtml  파일을 열고, 머리글 로우를 위한 <tr><th> 요소들을 다음에 강조된 코드로 대체합니다:

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
        </th>
        <th>
            First Name
        </th>
        <th>
            @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {

이 코드는 ViewBag 속성에 설정되어 있는 정보들을 이용해서 적절한 쿼리 문자열 값으로 하이퍼링크를 구성합니다.

이제 페이지를 실행한 다음, Last Name 컬럼이나 Enrollment Date 컬럼의 머리글을 클릭해보면 정렬 기능이 동작하는 것을 확인할 수 있을 것입니다.

가령, Last Name 컬럼 머리글을 클릭하면 성(Last Name)을 기준으로 학생들의 목록이 내림차순으로 정렬되어 출력됩니다.

Students Index 페이지에 검색 상자 추가하기

필터링 기능을 Students Index 페이지에 추가하려면, 뷰에 텍스트 상자와 전송(Submit) 버튼을 추가하고 Index 메서드도 그에 대응하도록 변경해야 합니다. 이 텍스트 박스에는 FirstMidName 필드와 LastName 필드에서 검색할 문자열을 입력하게 됩니다.

Index 메서드에 필터링 기능 추가하기

이번에도 Controllers\StudentController.cs  파일의 Index 메서드를 다음 코드로 대체합니다 (변경된 부분들이 강조되어 있습니다):

public ViewResult Index(string sortOrder, string searchString)
{
    ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in db.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                                    || s.FirstMidName.Contains(searchString));
    }
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }

    return View(students.ToList());
}

변경된 코드들을 살펴보면 Index 메서드에 searchString 매개변수가 추가된 것을 알 수 있습니다. 이 검색 문자열 값은 잠시 후 Index 뷰에 추가할 텍스트 상자로부터 입력받게 됩니다. 그리고 성이나 이름에 검색 문자열이 포함된 학생들만 선택하기 위한 where 절이 LINQ 구문에 추가되었습니다. 참고로 where 절을 추가하는 이 구문은 검색할 값이 존재하는 경우에만 실행된다는 점에 주의하시기 바랍니다.

노트: 상당히 많은 경우에 Entity Framework 엔티티 집합과 인-메모리 컬렉션에 대한 확장 메서드로 동일한 메서드를 호출할 수 있습니다. 일반적으로 그 결과 역시 동일한 경우가 대부분이지만 경우에 따라서는 전혀 다를 수도 있습니다.

이를테면 가장 대표적인 예로, .NET 프레임워크는 Contains 메서드에 빈 문자열을 전달할 경우 모든 로우들을 반환하도록 구현되어 있지만, SQL Server Compact 4.0용 Entity Framework 공급자는 빈 문자열에 대해 아무런 로우도 반환하지 않습니다. 따라서 이번 예제의 코드는 (즉, if 구문 안에 작성된 Where 구문은) 모든 버전의 SQL Server에서 동일한 결과를 반환합니다. 또한, .NET 프레임워크의 Contains 메서드는 기본적으로 대소문자를 구분해서 비교를 수행하도록 구현되어 있지만, Entity Framework SQL Server 공급자는 기본적으로 대소문자를 구분하지 않고 비교를 수행합니다. 그러므로, 이후에 IQueryable 개체 대신 IEnumerable 컬랙션을 반환하는 리파지터리를 사용하도록 코드를 변경한다면, ToUpper 메서드를 호출해서 명시적으로 대소문자를 구분하지 않도록 구현해야만 결과가 바뀌지 않습니다. (IEnumerable 컬랙션을 대상으로 Contains 메서드를 호출하면 .NET 프레임워크의 구현이 사용됩니다. 반면, IQueryable 개체를 대상으로 Contains 메서드를 호출하면 데이터베이스 공급자의 구현이 사용됩니다.)

그뿐만 아니라 Null 처리 방식도 각각의 데이터베이스 공급자에 따라, 또는 IQueryable 개체를 사용하는지 아니면 IEnumerable 컬랙션을 사용하는지에 따라 달라질 수 있습니다. 예를 들어서, 일부 상황에서는 table.Column != 0 같은 Where 조건을 지정할 경우, 컬럼 값으로 null 값을 갖고 있는 로우는 반환되지 않을 수 있습니다. 보다 자세한 정보는 Incorrect handling of null variables in 'where' clause 제안을 참고하시기 바랍니다.

Student Index 뷰에 검색 상자 추가하기

다음 코드에 강조된 부분들을 Views\Student\Index.cshtml  파일에서 table 태그가 시작되기 직전에 추가하여 캡션과 텍스트 상자, 그리고 Search 버튼을 생성합니다:

<p>
    @Html.ActionLink("Create New", "Create")
</p>

@using (Html.BeginForm())
{
    <p>
        Find by name: @Html.TextBox("SearchString")
        <input type="submit" value="Search" />
    </p>
}

<table>
    <tr>

이제 페이지를 실행한 다음, 검색 문자열을 입력하고 Search 버튼을 클릭해보면 필터링 기능이 동작하는 것을 확인할 수 있습니다.

이때 URL에 "an"이라는 검색 문자열이 포함되어 있지 않다는 점에 주의하시기 바랍니다. 다시 말해서 이 페이지를 북마크에 저장해 놓더라도, 다시 그 북마크를 열어보면 필터링된 목록이 출력되지는 않을 것이라는 뜻입니다. 본 자습서의 뒷부분에서는 쿼리 문자열을 통해서 필터링 기준을 관리하도록 Search 버튼을 변경하는 방법을 다시 살펴보겠습니다.

Students Index 페이지에 페이징 추가하기

페이징 기능을 추가하기 위한 첫번째 단계로 PagedList.Mvc NuGet 패키지를 설치해보겠습니다. 그런 다음, Index 메서드를 조금 더 개선해서 Index 뷰에 페이징 링크를 추가하겠습니다. 이 PagedList.Mvc 패키지는 다양한 ASP.NET MVC용 페이징 및 정렬 패키지들 중 하나로, 본문에서 이 패키지를 사용하는 이유는 단지 특정 사례를 보여주기 위한 것일뿐, 이 패키지가 다른 패키지들보다 훨씬 뛰어나서 추천하고자 하는 것은 아닙니다. 다음 그림은 이 페이징 링크가 적용된 모습을 보여줍니다.

PagedList.MVC NuGet 패키지 설치하기

NuGet PagedList.Mvc 패키지는 자신이 의존성을 갖고 있는 PagedList 패키지를 함께 설치합니다. PagedList 패키지에는 PagedList 컬랙션 형식과 IQueryableIEnumerable 컬랙션들에 대한 확장 메서드들이 포함되어 있습니다. 그리고 이 확장 메서드들은 IQueryable이나 IEnumerable로부터 한 페이지 분량의 데이터가 담긴 PagedList 컬랙션을 생성해주는데, 이 PagedList 컬랙션은 페이징 처리를 용이하게 만들어주는 몇 가지 속성들과 메서드들을 제공해줍니다. 또한 PagedList.Mvc 패키지는 페이징 버튼들을 출력해주는 페이징 헬퍼 메서드도 함께 설치해줍니다.

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

그리고 패키지 관리자 콘솔(Package Manager Console) 창에서 패키지 소스(Package source)nuget.org이고 기본 프로젝트(Default project)ContosoUniversity인지를 확인한 다음, 다음 명령을 입력합니다:

Install-Package PagedList.Mvc

잠시 후 작업이 끝나면 프로젝트를 빌드합니다.

Index 메서드에 페이징 기능 추가하기

계속해서 Controllers\StudentController.cs  파일에 PagedList 네임스페이스에 대한 using 구문을 추가합니다:

using PagedList;

그리고 Index 메서드를 다음 코드로 대체합니다:

public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
    ViewBag.CurrentSort = sortOrder;
    ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }
  
    ViewBag.CurrentFilter = searchString;
  
    var students = from s in db.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                                    || s.FirstMidName.Contains(searchString));
    }
    switch (sortOrder)
    {
        case "name_desc":
            students = students.OrderByDescending(s => s.LastName);
            break;
        case "Date":
            students = students.OrderBy(s => s.EnrollmentDate);
            break;
        case "date_desc":
            students = students.OrderByDescending(s => s.EnrollmentDate);
            break;
        default:  // Name ascending 
            students = students.OrderBy(s => s.LastName);
            break;
    }

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
}

코드를 살펴보면 메서드의 시그니처에 currentFilter 매개변수와 page 매개변수가 새로 추가되었음을 알 수 있습니다:

public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)

페이지가 최초로 출력되거나 사용자가 페이징 링크나 정렬 링크를 출력하지 않은 경우에는 모든 매개변수들의 값이 null입니다. 반면 페이징 링크가 클릭되면 page 변수에 출력될 페이지의 번호가 담겨집니다.

그리고 뷰에 현재 정렬 순서가 담긴 ViewBag 속성이 추가적으로 한 가지 더 제공되는데, 이는 페이징 중에도 동일한 정렬 순서를 유지하기 위해서는 페이징 링크에 정렬 순서 정보가 함께 포함되어 있어야 하기 때문입니다:

ViewBag.CurrentSort = sortOrder;

또 다른 속성인 ViewBag.CurrentFilter는 현재 지정된 필터 문자열 값을 뷰에 제공해줍니다. 페이징 중에 필터 설정을 관리하기 위해서는 반드시 이 값도 페이징 링크에 포함되어 있어야만 하며, 페이지가 다시 출력될 때마다 다시 텍스트 박스에 담겨져야만 합니다. 만약 페이징 도중 검색 문자열이 변경되면, 새로운 필터 값으로 인해 출력할 데이터의 결과 집합이 달라질 수 있으므로 페이지를 1로 재설정해야 합니다. 가령 텍스트 박스에 값을 입력하고 Search 버튼을 누르면 검색 문자열이 변경됩니다. 이 경우 searchString 매개변수에는 null이 아닌 값이 설정됩니다.

if (searchString != null)
{
    page = 1;
}
else
{
    searchString = currentFilter;
}

메서드의 마지막 부분에서는 students IQueryable 개체를 대상으로 ToPagedList 확장 메서드를 호출해서, student 쿼리를 페이징을 지원하는 컬랙션 형식에 담긴 단일 페이지 데이터로 변환합니다. 그런 다음, 이 단일 페이지 데이터를 뷰로 전달합니다:

int pageSize = 3;
int pageNumber = (page ?? 1);
return View(students.ToPagedList(pageNumber, pageSize));

ToPagedList 메서드는 페이지 번호를 인자로 받는데, 그 값을 설정하기 위한 구문에 사용된 두 개의 물음표는 null 병합 연산자(null-Coalescing Operator)입니다. null 병합 연산자는 nullable 형식에 대한 기본 값을 정의합니다. 즉, (page ?? 1)이라는 식은 page 변수의 값이 null이 아니면 그 값을 반환하고, 그 값이 null이면 1을 반환한다는 뜻입니다.

Student Index 뷰에 페이징 링크 추가하기

이번에는 Views\Student\Index.cshtml  파일의 기존 코드를 다음 코드로 대체합니다. 변경된 부분들이 코드에 강조되어 있습니다.

@model PagedList.IPagedList<ContosoUniversity.Models.Student>
@using PagedList.Mvc;

<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />

@{
    ViewBag.Title = "Students";
}

<h2>Students</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm("Index", "Student", FormMethod.Get))
{
    <p>
        Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
        <input type="submit" value="Search" />
    </p>
}
<table class="table">
    <tr>
        <th>
            @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
        </th>
        <th>
            First Name
        </th>
        <th>
            @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
        </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>
}

</table>
<br />
Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount

@Html.PagedListPager(Model, page => Url.Action("Index", 
    new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))

페이지 최상단에 작성된 @model 구문은 이제 뷰가 List 개체 대신 PagedList 개체를 전달 받음을 지정합니다. 그리고 PagedList.Mvc에 대한 using 구문은 페이징 버튼들을 생성하기 위한 MVC 헬퍼 메서드에 접근할 수 있게 해줍니다.

그리고 이 코드에서는 FormMethod.Get 인자를 지정할 수 있는 BeginForm 메서드의 오버로드 버전을 사용하고 있습니다.

@using (Html.BeginForm("Index", "Student", FormMethod.Get))
{
    <p>
        Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
        <input type="submit" value="Search" />
    </p>
}

기본적으로 BeginForm 메서드는 폼의 데이터를 POST 방식으로 제출하는데, 이 얘기는 매개변수들이 URL을 통해서 쿼리 문자열로 전달되는 것이 아니라 HTTP 메시지 본문에 담겨서 전달된다는 뜻입니다. 반면 이 코드에서처럼 HTTP GET 방식을 지정하면, 폼의 데이터가 URL을 통해서 쿼리 문자열로 전달되므로 사용자가 해당 URL을 북마크 할 수도 있습니다. W3C guidelines for the use of HTTP GET 문서에서는 특정 작업으로 인해서 데이터의 변경이 발생하지 않을 경우, GET 방식을 사용하도록 권장하고 있습니다.

또한 검색 텍스트 박스는 현재 지정된 검색 문자열로 초기화되므로, 새로운 페이지를 클릭하더라도 검색 문자열이 계속 유지됩니다.

 Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  

컬럼 머리글 링크 역시 필터된 결과가 반영된 상태로 정렬이 가능하도록 쿼리 문자열을 이용해서 현재 검색 문자열을 컨트롤러에 전달합니다:

 @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })

그리고 현재 페이지와 전체 페이지들의 수를 출력해줍니다.

Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount

만약 출력할 페이지가 존재하지 않는다면 "Page 0 of 0"이라는 문자열이 렌더될 것입니다. (이 경우, 현재 페이지 번호가 전체 페이지의 수보다 큰 상태가 됩니다. Model.PageNumber는 1인 반면, Model.PageCount는 0이기 때문입니다.)

페이징 버튼 자체는 PagedListPager 헬퍼 메서드에 의해서 출력됩니다:

@Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )

PagedListPager 헬퍼 메서드는 URL이나 디자인 스타일 등을 사용자 지정할 수 있는 몇 가지 옵션들을 제공해줍니다. 더 자세한 정보는 GitHub 사이트의 TroyGoode / PagedList 리파지터리를 참고하시기 바랍니다.

이제 페이지를 실행해봅니다.

다양한 정렬 순서를 지정한 상태에서 페이징 링크를 클릭해서 페이징이 정상적으로 동작하는지 확인해보시기 바랍니다. 그리고 검색 문자열을 지정한 상태로 페이징을 다시 시도해서 정렬이나 필터링 상태와 상관 없이 페이징이 정상적으로 동작하는지 점검해보시기 바랍니다.

학생들에 대한 통계 자료를 보여주는 정보(About) 페이지 만들기

마지막으로 Contoso University 웹 사이트의 About 페이지에 수강일자 별로 얼마나 많은 학생들이 수강을 했는지에 대한 정보를 출력해보겠습니다. 이 작업에는 그룹핑과 해당 그룹들에 대한 간단한 계산이 요구됩니다. 이를 위해 다음과 같은 작업들을 수행해보게 될 것입니다:

  • 뷰에 전달할 데이터를 담기 위한 뷰 모델 클래스를 생성합니다.
  • Home 컨트롤러의 About 메서드를 수정합니다.
  • About 뷰를 수정합니다.

뷰 모델 생성하기

프로젝트 루트 폴더 하위에 ViewModels 라는 이름의 폴더를 새로 생성하고, 이 폴더에 EnrollmentDateGroup.cs 라는 이름으로 클래스 파일을 추가한 다음, 템플릿 코드를 다음 코드로 대체합니다:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }
        
        public int StudentCount { get; set; }
    }
}

Home 컨트롤러 수정하기

이번에는 HomeController.cs  파일의 최상단에 다음과 같은 using 구문을 추가합니다:

using ContosoUniversity.DAL;
using ContosoUniversity.ViewModels;

그리고 클래스 선언의 직후의 여는 중괄호 다음에 데이터베이스 컨텍스트를 담기 위한 클래스 변수를 추가합니다:

public class HomeController : Controller
{
    private SchoolContext db = new SchoolContext();

계속해서 About 메서드를 다음 코드로 대체합니다:

public ActionResult About()
{
    IQueryable<EnrollmentDateGroup> data = from student in db.Students
                                           group student by student.EnrollmentDate into dateGroup
                                           select new EnrollmentDateGroup()
                                           {
                                               EnrollmentDate = dateGroup.Key,
                                               StudentCount = dateGroup.Count()
                                           };

    return View(data.ToList());
}

이 LINQ 구문은 수강일자를 기준으로 Student 엔터티들을 그룹핑하고, 각 그룹들에 포함된 엔터티들의 개수를 계산한 다음, 그 결과를 EnrollmentDateGroup 뷰 모델 개체들의 컬랙션에 담습니다.

마지막으로 Dispose 메서드를 추가합니다:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

About 뷰 수정하기

이번에는 Views\Home\About.cshtml  파일의 코드를 다음 코드로 대체합니다:

@model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
           
@{
    ViewBag.Title = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
    <tr>
        <th>
            Enrollment Date
        </th>
        <th>
            Students
        </th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.EnrollmentDate)
        </td>
        <td>
            @item.StudentCount
        </td>
    </tr>
}
</table>

이제 응용 프로그램을 실행하고 정보(About) 링크를 클릭해봅니다. 그러면 수강일자별 학생의 수가 테이블 형식으로 출력되는 것을 확인할 수 있습니다.

요약

지금까지 세 편의 자습서를 통해서 데이터 모델을 생성하고 기본적인 CRUD, 정렬, 필터링, 페이징 및 그룹핑 기능을 구현하는 방법을 살펴봤습니다. 다음 자습서에서는 데이터 모델을 확장해보면서 보다 고급의 주제들에 관해서 살펴보도록 하겠습니다.

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

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

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