다음을 통해 공유


자습서: ASP.NET MVC 앱에서 EF와 함께 비동기 및 저장 프로시저 사용

이전 자습서에서는 동기 프로그래밍 모델을 사용하여 데이터를 읽고 업데이트하는 방법을 알아보았습니다. 이 자습서에서는 비동기 프로그래밍 모델을 구현하는 방법을 알아보세요. 비동기 코드는 서버 리소스를 더 잘 사용하기 때문에 애플리케이션의 성능을 향상하는 데 도움이 될 수 있습니다.

이 자습서에서는 엔터티에 대한 삽입, 업데이트 및 삭제 작업에 저장 프로시저를 사용하는 방법도 알아보세요.

마지막으로, 처음 배포한 이후 구현한 모든 데이터베이스 변경 내용과 함께 애플리케이션을 Azure에 다시 배포합니다.

다음 그림에서는 사용할 일부 페이지를 보여 줍니다.

부서 페이지

부서 만들기

이 자습서에서는 다음을 수행합니다.

  • 비동기 코드에 대해 알아보기
  • 부서 컨트롤러 만들기
  • 저장 프로시저를 사용합니다.
  • Azure에 배포

필수 조건

비동기 코드를 사용하는 이유

웹 서버에는 사용할 수 있는 스레드 수가 제한적이며, 로드 양이 많은 상황에서는 사용 가능한 모든 스레드가 사용될 수 있습니다. 이 경우 서버는 스레드가 해제될 때까지 새 요청을 처리할 수 없습니다. 동기 코드를 사용하면 I/O 완료를 대기하느라 작업을 실제로 수행하지 않는 동안에 많은 스레드가 정체될 수 있습니다. 비동기 코드를 사용하면 프로세스가 I/O 완료를 대기할 때 다른 요청을 처리하는 데 사용하도록 해당 스레드가 서버에서 해제됩니다. 따라서 비동기 코드를 사용하면 서버 리소스를 보다 효율적으로 사용할 수 있으며 서버는 지연 없이 더 많은 트래픽을 처리할 수 있습니다.

이전 버전의 .NET에서는 비동기 코드 작성 및 테스트가 복잡하고 오류가 발생하기 쉬우며 디버그하기 어려웠습니다. .NET 4.5에서는 비동기 코드 작성, 테스트 및 디버깅이 훨씬 쉬워서 비동기 코드를 작성할 이유가 없는 한 일반적으로 비동기 코드를 작성해야 합니다. 비동기 코드는 적은 양의 오버헤드를 발생시키지만 트래픽이 적은 상황에서는 성능 저하가 무시할 수 있지만 트래픽이 많은 상황에서는 잠재적인 성능 향상이 상당합니다.

비동기 프로그래밍에 대한 자세한 내용은 .NET 4.5의 비동기 지원을 사용하여 호출 차단을 방지합니다.

부서 컨트롤러 만들기

이전 컨트롤러와 동일한 방식으로 부서 컨트롤러를 만듭니다. 이번에는 비동기 컨트롤러 작업 사용 확인란을 선택합니다.

다음 강조 표시는 메서드를 비동기식으로 만들기 위해 동기 코드에 Index 추가된 내용을 보여 줍니다.

public async Task<ActionResult> Index()
{
    var departments = db.Departments.Include(d => d.Administrator);
    return View(await departments.ToListAsync());
}

Entity Framework 데이터베이스 쿼리를 비동기적으로 실행할 수 있도록 네 가지 변경 내용이 적용되었습니다.

  • 메서드는 메서드 본문의 일부에 대한 콜백을 생성하고 반환되는 개체를 자동으로 만들 Task<ActionResult> 도록 컴파일러에 지시하는 키워드로 표시됩니다async.
  • 반환 형식이 .로 ActionResult Task<ActionResult>변경되었습니다. 형식은 Task<T> 형식 T의 결과와 함께 진행 중인 작업을 나타냅니다.
  • 키워드가 await 웹 서비스 호출에 적용되었습니다. 컴파일러에서 이 키워드를 볼 때 백그라운드에서 메서드를 두 부분으로 분할합니다. 첫 번째 부분은 비동기적으로 시작되는 작업으로 끝납니다. 두 번째 부분은 작업이 완료되면 호출되는 콜백 메서드에 배치됩니다.
  • 확장 메서드의 ToList 비동기 버전이 호출되었습니다.

departments.ToList 문이 수정되었지만 문이 아닌 departments = db.Departments 이유는 무엇인가요? 그 이유는 쿼리 또는 명령을 데이터베이스로 보내게 하는 문만 비동기적으로 실행되기 때문입니다. 이 문은 departments = db.Departments 쿼리를 설정하지만 메서드가 호출될 때까지 쿼리가 ToList 실행되지 않습니다. 따라서 메서드만 ToList 비동기적으로 실행됩니다.

Details 메서드 및 Delete HttpGet Edit 메서드 Find 에서 메서드는 쿼리를 데이터베이스로 보내도록 하는 메서드이므로 비동기적으로 실행되는 메서드입니다.

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Department department = await db.Departments.FindAsync(id);
    if (department == null)
    {
        return HttpNotFound();
    }
    return View(department);
}

Create, HttpPost EditDeleteConfirmed 메서드에서는 메모리의 SaveChanges 엔터티만 수정할 수 있는 문이 db.Departments.Add(department) 아니라 명령이 실행되도록 하는 메서드 호출입니다.

public async Task<ActionResult> Create(Department department)
{
    if (ModelState.IsValid)
    {
        db.Departments.Add(department);
    await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }

Views\Department\Index.cshtml을 열고 템플릿 코드를 다음 코드로 바꿉니다.

@model IEnumerable<ContosoUniversity.Models.Department>
@{
    ViewBag.Title = "Departments";
}
<h2>Departments</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Budget)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.StartDate)
        </th>
    <th>
            Administrator
        </th>
        <th></th>
    </tr>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Budget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.StartDate)
        </td>
    <td>
            @Html.DisplayFor(modelItem => item.Administrator.FullName)
            </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
            @Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
        </td>
    </tr>
}
</table>

이 코드는 제목을 인덱스에서 부서로 변경하고, 관리자 이름을 오른쪽으로 이동하고, 관리자의 전체 이름을 제공합니다.

만들기, 삭제, 세부 정보 및 편집 보기에서 과정 보기에서 부서 이름 필드를 "부서"로 변경한 것과 같은 방식으로 필드의 캡션 InstructorID 을 "관리자"로 변경합니다.

만들기 및 편집 보기에서 다음 코드를 사용합니다.

<label class="control-label col-md-2" for="InstructorID">Administrator</label>

삭제 및 세부 정보 보기에서 다음 코드를 사용합니다.

<dt>
    Administrator
</dt>

애플리케이션을 실행하고 부서 탭을 클릭합니다.

모든 것이 다른 컨트롤러와 동일하게 작동하지만 이 컨트롤러에서는 모든 SQL 쿼리가 비동기적으로 실행됩니다.

Entity Framework에서 비동기 프로그래밍을 사용할 때 알아야 할 몇 가지 사항은 다음과 같습니다.

  • 비동기 코드는 스레드로부터 안전하지 않습니다. 즉, 동일한 컨텍스트 인스턴스를 사용하여 여러 작업을 병렬로 수행하지 마세요.
  • 비동기 코드의 성능 이점을 활용하려는 경우 사용 중인(예: 페이징) 라이브러리 패키지 또한 쿼리를 데이터베이스에 전송하도록 하는 Entity Framework 메서드를 호출하는 경우 비동기를 사용하는지 확인합니다.

저장 프로시저를 사용합니다.

일부 개발자와 DBA는 데이터베이스 액세스에 저장 프로시저를 사용하는 것을 선호합니다. 이전 버전의 Entity Framework에서는 원시 SQL 쿼리를 실행하여 저장 프로시저를 사용하여 데이터를 검색할 수 있지만 업데이트 작업에 저장 프로시저를 사용하도록 EF에 지시할 수는 없습니다. EF 6에서는 저장 프로시저를 사용하도록 Code First를 쉽게 구성할 수 있습니다.

  1. DAL\SchoolContext.cs 강조 표시된 코드를 메서드에 OnModelCreating 추가합니다.

    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("InstructorID")
                .ToTable("CourseInstructor"));
        modelBuilder.Entity<Department>().MapToStoredProcedures();
    }
    

    이 코드는 엔터티에 대한 삽입, 업데이트 및 삭제 작업에 저장 프로시저를 사용하도록 Entity Framework에 Department 지시합니다.

  2. 패키지 관리 콘솔에서 다음 명령을 입력합니다.

    add-migration DepartmentSP

    Migrations\<timestamp>_DepartmentSP.cs를 열어 저장 프로시저 삽입, 업데이트 및 삭제를 만드는 메서드의 Up 코드를 확인합니다.

    public override void Up()
    {
        CreateStoredProcedure(
            "dbo.Department_Insert",
            p => new
                {
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID])
                  VALUES (@Name, @Budget, @StartDate, @InstructorID)
                  
                  DECLARE @DepartmentID int
                  SELECT @DepartmentID = [DepartmentID]
                  FROM [dbo].[Department]
                  WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity()
                  
                  SELECT t0.[DepartmentID]
                  FROM [dbo].[Department] AS t0
                  WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Update",
            p => new
                {
                    DepartmentID = p.Int(),
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"UPDATE [dbo].[Department]
                  SET [Name] = @Name, [Budget] = @Budget, [StartDate] = @StartDate, [InstructorID] = @InstructorID
                  WHERE ([DepartmentID] = @DepartmentID)"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Delete",
            p => new
                {
                    DepartmentID = p.Int(),
                },
            body:
                @"DELETE [dbo].[Department]
                  WHERE ([DepartmentID] = @DepartmentID)"
        );    
    }
    
  3. 패키지 관리 콘솔에서 다음 명령을 입력합니다.

    update-database

  4. 디버그 모드에서 애플리케이션을 실행하고 부서 탭을 클릭한 다음 새로 만들기를 클릭합니다.

  5. 새 부서의 데이터를 입력한 다음 만들기를 클릭합니다.

  6. Visual Studio에서 출력 창의 로그를 확인하여 저장 프로시저가 새 부서 행을 삽입하는 데 사용되었는지 확인합니다.

    부서 삽입 SP

Code First는 기본 저장 프로시저 이름을 만듭니다. 기존 데이터베이스를 사용하는 경우 데이터베이스에 이미 정의된 저장 프로시저를 사용하려면 저장 프로시저 이름을 사용자 지정해야 할 수 있습니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 Entity Framework Code First Insert/Update/Delete 저장 프로시저를 참조하세요.

생성된 저장 프로시저의 작업을 사용자 지정하려는 경우 저장 프로시저를 만드는 마이그레이션 Up 메서드에 대한 스캐폴드 코드를 편집할 수 있습니다. 이렇게 하면 마이그레이션이 실행될 때마다 변경 내용이 반영되고 배포 후 프로덕션에서 마이그레이션이 자동으로 실행될 때 프로덕션 데이터베이스에 적용됩니다.

이전 마이그레이션에서 만든 기존 저장 프로시저를 변경하려면 추가 마이그레이션 명령을 사용하여 빈 마이그레이션을 생성한 다음 AlterStoredProcedure 메서드를 호출하는 코드를 수동으로 작성할 수 있습니다.

Azure에 배포

이 섹션에서는 이 시리즈의 마이그레이션 및 배포 자습서에서 선택적으로 Azure앱 배포 섹션을 완료해야 합니다. 로컬 프로젝트에서 데이터베이스를 삭제하여 해결한 마이그레이션 오류가 있는 경우 이 섹션을 건너뜁니다.

  1. Visual Studio의 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 상황에 맞는 메뉴에서 게시를 선택합니다.

  2. 게시를 클릭합니다.

    Visual Studio는 애플리케이션을 Azure에 배포하고 애플리케이션은 Azure에서 실행되는 기본 브라우저에서 열립니다.

  3. 애플리케이션을 테스트하여 작동하는지 확인합니다.

    데이터베이스에 액세스하는 페이지를 처음 실행할 때 Entity Framework는 현재 데이터 모델을 사용하여 데이터베이스를 최신 상태로 만드는 데 필요한 모든 마이그레이션 Up 방법을 실행합니다. 이제 이 자습서에서 추가한 부서 페이지를 포함하여 마지막으로 배포한 이후 추가한 모든 웹 페이지를 사용할 수 있습니다.

코드 가져오기

완료된 프로젝트 다운로드

추가 리소스

다른 Entity Framework 리소스에 대한 링크는 ASP.NET 데이터 액세스 - 권장 리소스에서 찾을 수 있습니다.

다음 단계

이 자습서에서는 다음을 수행합니다.

  • 비동기 코드에 대해 알아보았습니다.
  • 부서 컨트롤러 만들기
  • 사용된 저장 프로시저
  • Azure에 배포됨

다음 문서로 이동하여 여러 사용자가 동시에 동일한 엔터티를 업데이트할 때 충돌을 처리하는 방법을 알아봅니다.