ASP.NET 4 웹 애플리케이션에서 Entity Framework 4.0과의 동시성 처리

작성자: Tom Dykstra

이 자습서 시리즈는 Entity Framework 4.0 자습서 시리즈를 사용하여 시작 만든 Contoso University 웹 애플리케이션을 기반으로 합니다. 이전 자습서를 완료하지 않은 경우 이 자습서의 시작점으로 만든 애플리케이션을 다운로드 할 수 있습니다. 전체 자습서 시리즈에서 만든 애플리케이션을 다운로드 할 수도 있습니다. 자습서에 대한 질문이 있는 경우 ASP.NET Entity Framework 포럼에 게시할 수 있습니다.

이전 자습서에서는 컨트롤 및 Entity Framework를 ObjectDataSource 사용하여 데이터를 정렬하고 필터링하는 방법을 알아보았습니다. 이 자습서에서는 Entity Framework를 사용하는 ASP.NET 웹 애플리케이션에서 동시성을 처리하는 옵션을 보여줍니다. 강사 사무실 과제를 업데이트하는 데 전념하는 새 웹 페이지를 만듭니다. 해당 페이지와 이전에 만든 부서 페이지에서 동시성 문제를 처리합니다.

Image06

Image01

동시성 충돌

동시성 충돌은 한 사용자가 레코드를 편집하고 다른 사용자가 첫 번째 사용자의 변경 내용이 데이터베이스에 기록되기 전에 동일한 레코드를 편집할 때 발생합니다. 이러한 충돌을 감지하도록 Entity Framework를 설정하지 않은 경우 데이터베이스를 마지막으로 업데이트하는 사람은 다른 사용자의 변경 내용을 덮어씁니다. 많은 애플리케이션에서 이 위험은 허용되며 가능한 동시성 충돌을 처리하도록 애플리케이션을 구성할 필요가 없습니다. (사용자가 적거나 업데이트가 거의 없거나 일부 변경 내용을 덮어쓰는 경우 중요하지 않은 경우 동시성 프로그래밍 비용이 이점보다 클 수 있습니다.) 동시성 충돌에 대해 걱정할 필요가 없는 경우 이 자습서를 건너뛸 수 있습니다. 시리즈의 나머지 두 자습서는 이 자습서에서 빌드한 항목에 의존하지 않습니다.

비관적 동시성(잠금)

애플리케이션에서 동시성 시나리오에서 실수로 인한 데이터 손실을 방지할 필요가 있는 경우 해당 작업을 수행하는 한 가지 방법은 데이터베이스 잠금을 사용하는 것입니다. 이를 비관적 동시성이라고 합니다. 예를 들어 데이터베이스에서 행을 읽기 전에 읽기 전용 또는 업데이트 액세스에 대한 잠금을 요청합니다. 업데이트 액세스에 대한 행을 잠그는 경우 변경 중인 데이터의 복사본을 가져오기 때문에 다른 사용자는 읽기 전용 또는 업데이트 액세스에 대한 행을 잠그도록 허용되지 않습니다. 읽기 전용 액세스에 대한 행을 잠그는 경우 다른 사용자도 읽기 전용에 대해 잠글 수 있지만 업데이트에 대해서는 잠글 수 없습니다.

잠금 관리에는 몇 가지 단점이 있습니다. 프로그램을 설정하는 데 복잡할 수 있습니다. 상당한 데이터베이스 관리 리소스가 필요하며 애플리케이션 사용자 수가 증가함에 따라 성능 문제가 발생할 수 있습니다(즉, 잘 확장되지 않음). 이러한 이유로 모든 데이터베이스 관리 시스템은 비관적 동시성을 지원하지 않습니다. Entity Framework는 기본 제공 지원을 제공하지 않으며 이 자습서에서는 구현 방법을 보여 주지 않습니다.

낙관적 동시성

비관적 동시성에 대한 대안은 낙관적 동시성입니다. 낙관적 동시성은 동시성 충돌 발생을 허용한 다음, 그럴 경우 적절하게 반응하는 것을 의미합니다. 예를 들어 John은 Department.aspx 페이지를 실행하고 기록 부서 의 편집 링크를 클릭하고 예산 금액을 $1,000,000.00에서 $125,000.00로 줄입니다. (John은 경쟁 부서를 관리하고 자신의 부서를 위해 돈을 확보하려고 합니다.)

Image07

John이 업데이트를 클릭하기 전에 Jane은 동일한 페이지를 실행하고 기록 부서에 대한 편집 링크를 클릭한 다음 시작 날짜 필드를 2011년 1월 10일에서 1999년 1월 1일로 변경합니다. (Jane은 역사 부서를 관리하며 더 많은 연급자를 제공하고자 합니다.)

Image08

John이 먼저 업데이트를 클릭한 다음 Jane이 업데이트를 클릭합니다. Jane의 브라우저는 이제 예산 금액을 $1,000,000.00로 나열하지만 John이 금액이 $125,000.00로 변경되었기 때문에 올바르지 않습니다.

이 시나리오에서 수행할 수 있는 작업 중 일부는 다음과 같습니다.

  • 사용자가 수정한 속성의 추적을 유지하고 데이터베이스에서 해당하는 열만 업데이트할 수 있습니다. 예제 시나리오에서 서로 다른 속성이 두 사용자에 의해 업데이트되었기 때문에 데이터가 손실되지 않습니다. 다음에 누군가가 역사 부서를 찾아볼 때 1999년 1월 1일과 $125,000.00가 표시됩니다.

    이는 Entity Framework의 기본 동작이며 데이터 손실을 초래할 수 있는 충돌 수를 크게 줄일 수 있습니다. 그러나 이 동작은 엔터티의 동일한 속성에 대한 경쟁 변경이 수행되는 경우 데이터 손실을 방지하지 않습니다. 또한 이 동작이 항상 가능한 것은 아닙니다. 저장 프로시저를 엔터티 형식에 매핑하면 데이터베이스에서 엔터티를 변경하면 엔터티의 모든 속성이 업데이트됩니다.

  • 제인의 변화가 존의 변화를 덮어쓰도록 할 수 있습니다. Jane이 업데이트를 클릭하면 예산 금액이 $1,000,000.00로 돌아갑니다. 이를 클라이언트 우선 또는 최종 우선 시나리오라고 합니다. (클라이언트의 값이 데이터 저장소에 있는 값보다 우선합니다.)

  • Jane의 변경 내용이 데이터베이스에서 업데이트되지 않도록 방지할 수 있습니다. 일반적으로 오류 메시지를 표시하고, 데이터의 현재 상태를 표시하고, 변경 내용을 계속 만들려는 경우 변경 내용을 다시 입력할 수 있습니다. 입력을 저장하고 다시 입력하지 않고도 다시 적용할 수 있는 기회를 제공하여 프로세스를 더욱 자동화할 수 있습니다. 이를 저장소 우선 시나리오라고 합니다. (데이터 저장소 값은 클라이언트가 전송한 값에 우선합니다.)

동시성 충돌 검색

Entity Framework에서 Entity Framework에서 throw하는 예외를 처리하여 OptimisticConcurrencyException 충돌을 resolve 수 있습니다. 이러한 예외를 throw하는 시기를 확인하기 위해 Entity Framework에서 충돌을 검색할 수 있어야 합니다. 따라서 데이터베이스와 데이터 모델을 적절하게 구성해야 합니다. 충돌 검색을 활성화하기 위한 몇 가지 옵션은 다음과 같습니다.

  • 데이터베이스에 행이 변경된 시기를 결정하는 데 사용할 수 있는 테이블 열을 포함합니다. 그런 다음 SQL Update 또는 Delete 명령의 절에 Where 해당 열을 포함하도록 Entity Framework를 구성할 수 있습니다.

    이것이 테이블의 Timestamp 열 용도입니다 OfficeAssignment .

    Image09

    열의 데이터 형식을 Timestamp 이라고도 합니다 Timestamp. 그러나 열에는 실제로 날짜 또는 시간 값이 포함되지 않습니다. 대신 값은 행이 업데이트될 때마다 증가하는 순차적인 숫자입니다. Update 또는 Delete 명령에서 절에는 Where 원래 Timestamp 값이 포함됩니다. 업데이트할 행이 다른 사용자에 의해 변경된 경우 의 Timestamp 값은 원래 값과 다르므로 절은 Where 업데이트할 행을 반환하지 않습니다. Entity Framework에서 현재 Update 또는 Delete 명령에 의해 업데이트된 행이 없음을 발견하면(즉, 영향을 받는 행 수가 0인 경우) 동시성 충돌로 해석됩니다.

  • Delete 명령의 절 Update 에 테이블에 Where 있는 모든 열의 원래 값을 포함하도록 Entity Framework를 구성합니다.

    첫 번째 옵션에서와 같이 행을 처음 읽 Where 은 이후 행의 내용이 변경된 경우 절은 엔터티 프레임워크가 동시성 충돌로 해석하는 업데이트할 행을 반환하지 않습니다. 이 메서드는 필드를 사용하는 Timestamp 것만큼 효과적이지만 비효율적일 수 있습니다. 열이 많은 데이터베이스 테이블의 경우 매우 큰 Where 절이 발생할 수 있으며 웹 애플리케이션에서는 많은 양의 상태를 유지해야 할 수 있습니다. 많은 양의 상태를 유지 관리하려면 서버 리소스(예: 세션 상태)가 필요하거나 웹 페이지 자체(예: 상태 보기)에 포함되어야 하므로 애플리케이션 성능에 영향을 줄 수 있습니다.

이 자습서에서는 추적 속성(엔터티)이 없는 엔터티와 추적 OfficeAssignment 속성(Department엔터티)이 있는 엔터티에 대해 낙관적 동시성 충돌에 대한 오류 처리를 추가합니다.

추적 속성 없이 낙관적 동시성 처리

추적(Timestamp) 속성이 없는 엔터티에 대해 Department 낙관적 동시성을 구현하려면 다음 작업을 완료합니다.

  • 엔터티에 대한 동시성 추적 Department 을 사용하도록 데이터 모델을 변경합니다.
  • 클래스에서 메서드의 SchoolRepository 동시성 예외를 SaveChanges 처리합니다.
  • Departments.aspx 페이지에서 시도된 변경이 실패했음을 경고하는 메시지를 사용자에게 표시하여 동시성 예외를 처리합니다. 그러면 사용자는 현재 값을 보고 여전히 필요한 경우 변경 내용을 다시 시도할 수 있습니다.

데이터 모델에서 동시성 추적 사용

Visual Studio에서 이 시리즈의 이전 자습서에서 작업하던 Contoso University 웹 애플리케이션을 엽니다.

SchoolModel.edmx를 열고 데이터 모델 디자이너에서 엔터티의 NameDepartment 속성을 마우스 오른쪽 단추로 클릭한 다음 속성을 클릭합니다. 속성 창에서 속성을 로 ConcurrencyMode 변경합니다Fixed.

Image16

기본 키가 아닌 다른 스칼라 속성(Budget, StartDateAdministrator)에도 동일한 작업을 수행합니다. 탐색 속성에는 이 작업을 수행할 수 없습니다. 이렇게 하면 Entity Framework에서 데이터베이스의 엔터티를 업데이트 Department 하는 또는 Delete SQL 명령을 생성 Update 할 때마다 이러한 열(원래 값 포함)이 절에 Where 포함되어야 합니다. 또는 Delete 명령이 실행될 때 Update 행을 찾을 수 없는 경우 Entity Framework는 낙관적 동시성 예외를 throw합니다.

데이터 모델을 저장하고 닫습니다.

DAL에서 동시성 예외 처리

SchoolRepository.cs를 열고 네임스페이스에 대해 System.Data 다음 using 문을 추가합니다.

using System.Data;

낙관적 동시성 예외를 처리하는 다음 새 SaveChanges 메서드를 추가합니다.

public void SaveChanges()
{
    try
    {
        context.SaveChanges();
    }
    catch (OptimisticConcurrencyException ocex)
    {
        context.Refresh(RefreshMode.StoreWins, ocex.StateEntries[0].Entity);
        throw ocex;
    }
}

이 메서드를 호출할 때 동시성 오류가 발생하면 메모리에 있는 엔터티의 속성 값이 현재 데이터베이스에 있는 값으로 바뀝니다. 동시성 예외는 웹 페이지에서 처리할 수 있도록 다시 발생합니다.

DeleteDepartmentUpdateDepartment 메서드에서 기존 호출을 호출로 context.SaveChanges()SaveChanges() 바꿔 새 메서드를 호출합니다.

프레젠테이션 계층에서 동시성 예외 처리

Departments.aspx를 열고 컨트롤에 OnDeleted="DepartmentsObjectDataSource_Deleted"DepartmentsObjectDataSource 특성을 추가합니다. 이제 컨트롤의 여는 태그가 다음 예제와 유사합니다.

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.BLL.SchoolBL" DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartmentsByName" DeleteMethod="DeleteDepartment" UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues" OldValuesParameterFormatString="orig{0}" 
        OnUpdated="DepartmentsObjectDataSource_Updated" SortParameterName="sortExpression" 
        OnDeleted="DepartmentsObjectDataSource_Deleted" >

DepartmentsGridView 다음 예제와 같이 컨트롤에서 특성의 DataKeyNames 모든 테이블 열을 지정합니다. 이렇게 하면 매우 큰 뷰 상태 필드가 생성되며, 이는 일반적으로 추적 필드를 사용하는 것이 동시성 충돌을 추적하는 데 선호되는 방법인 이유 중 하나입니다.

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" 
        DataKeyNames="DepartmentID,Name,Budget,StartDate,Administrator" 
        OnRowUpdating="DepartmentsGridView_RowUpdating"
        OnRowDataBound="DepartmentsGridView_RowDataBound"
        AllowSorting="True" >

Departments.aspx.cs를 열고 네임스페이스에 대해 System.Data 다음 using 문을 추가합니다.

using System.Data;

동시성 예외를 처리하기 위해 데이터 소스 컨트롤 UpdatedDeleted 이벤트 처리기에서 호출할 다음 새 메서드를 추가합니다.

private void CheckForOptimisticConcurrencyException(ObjectDataSourceStatusEventArgs e, string function)
{
    if (e.Exception.InnerException is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

이 코드는 예외 형식을 확인하고 동시성 예외인 경우 코드는 컨트롤에 메시지를 ValidationSummary 표시하는 컨트롤을 동적으로 만듭니다CustomValidator.

이전에 추가한 Updated 이벤트 처리기에서 새 메서드를 호출합니다. 또한 동일한 메서드를 호출하는 새 Deleted 이벤트 처리기를 만듭니다(하지만 다른 작업은 수행하지 않음).

protected void DepartmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "update");
        // ...
    }
}

protected void DepartmentsObjectDataSource_Deleted(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        CheckForOptimisticConcurrencyException(e, "delete");
    }
}

부서 페이지에서 낙관적 동시성 테스트

Departments.aspx 페이지를 실행합니다.

부서 페이지를 보여 주는 스크린샷

행에서 편집 을 클릭하고 예산 열의 값을 변경합니다. (기존 School 데이터베이스 레코드에 잘못된 데이터가 포함되어 있으므로 이 자습서에 대해 만든 레코드만 편집할 수 있습니다. 경제 부서의 기록은 실험하기에 안전한 기록입니다.)

Image18

새 브라우저 창을 열고 페이지를 다시 실행합니다(첫 번째 브라우저 창의 주소 상자에서 두 번째 브라우저 창으로 URL 복사).

입력할 준비가 된 새 브라우저 창을 보여 주는 스크린샷

이전에 편집한 동일한 행에서 편집 을 클릭하고 예산 값을 다른 항목으로 변경합니다.

Image19

두 번째 브라우저 창에서 업데이트를 클릭합니다. 예산 금액이 이 새 값으로 변경되었습니다.

Image20

첫 번째 브라우저 창에서 업데이트를 클릭합니다. 업데이트가 실패합니다. 예산 금액은 두 번째 브라우저 창에서 설정한 값을 사용하여 다시 표시되며 오류 메시지가 표시됩니다.

Image21

추적 속성을 사용하여 낙관적 동시성 처리

추적 속성이 있는 엔터티에 대한 낙관적 동시성을 처리하려면 다음 작업을 완료합니다.

  • 데이터 모델에 저장 프로시저를 추가하여 엔터티를 관리 OfficeAssignment 합니다. (추적 속성 및 저장 프로시저를 함께 사용할 필요는 없습니다. 여기서는 설명을 위해 그룹화하기만 하면 됩니다.)
  • DAL에서 낙관적 동시성 예외를 처리하는 코드를 포함하여 엔터티의 OfficeAssignment 경우 DAL 및 BLL에 CRUD 메서드를 추가합니다.
  • Office 과제 웹 페이지를 만듭니다.
  • 새 웹 페이지에서 낙관적 동시성을 테스트합니다.

데이터 모델에 OfficeAssignment 저장 프로시저 추가

모델 디자이너에서 SchoolModel.edmx 파일을 열고 디자인 화면을 마우스 오른쪽 단추로 클릭한 다음 데이터베이스에서 모델 업데이트를 클릭합니다. 데이터베이스 개체 선택 대화 상자의 추가 탭에서 저장 프로시저를 확장하고 세 OfficeAssignment 가지 저장 프로시저(다음 스크린샷 참조)를 선택한 다음 마침을 클릭합니다. (이러한 저장 프로시저는 스크립트를 사용하여 다운로드하거나 만들 때 데이터베이스에 이미 있었습니다.)

Image02

엔터티를 마우스 오른쪽 단추로 OfficeAssignment 클릭하고 저장 프로시저 매핑을 선택합니다.

Image03

해당 저장 프로시저를 사용하도록 삽입, 업데이트삭제 함수를 설정합니다. 함수의 OrigTimestamp 매개 변수에 Update 대해 속성을Timestamp 로 설정하고 원래 값 사용 옵션을 선택합니다.

Image04

Entity Framework가 저장 프로시저를 UpdateOfficeAssignment 호출하면 매개 변수에 있는 OrigTimestamp 열의 Timestamp 원래 값을 전달합니다. 저장 프로시저는 절에서 이 매개 변수를 Where 사용합니다.

ALTER PROCEDURE [dbo].[UpdateOfficeAssignment]
    @InstructorID int,
    @Location nvarchar(50),
    @OrigTimestamp timestamp
    AS
    UPDATE OfficeAssignment SET Location=@Location 
    WHERE InstructorID=@InstructorID AND [Timestamp]=@OrigTimestamp;
    IF @@ROWCOUNT > 0
    BEGIN
        SELECT [Timestamp] FROM OfficeAssignment 
            WHERE InstructorID=@InstructorID;
    END

또한 저장 프로시저는 Entity Framework가 메모리에 있는 엔터티를 해당 데이터베이스 행과 동기화된 상태로 유지할 OfficeAssignment 수 있도록 업데이트 후 열의 새 값을 Timestamp 선택합니다.

(사무실 할당을 삭제하기 위한 저장 프로시저에는 매개 변수가 OrigTimestamp 없습니다. 이 때문에 Entity Framework는 엔터티를 삭제하기 전에 엔터티가 변경되지 않았는지 확인할 수 없습니다.)

데이터 모델을 저장하고 닫습니다.

DAL에 OfficeAssignment 메서드 추가

ISchoolRepository.cs를 열고 엔터티 집합에 대해 다음 CRUD 메서드를 OfficeAssignment 추가합니다.

IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression);
void InsertOfficeAssignment(OfficeAssignment OfficeAssignment);
void DeleteOfficeAssignment(OfficeAssignment OfficeAssignment);
void UpdateOfficeAssignment(OfficeAssignment OfficeAssignment, OfficeAssignment origOfficeAssignment);

SchoolRepository.cs에 다음 새 메서드를 추가합니다. 메서드에서 UpdateOfficeAssignment 대신 로컬 SaveChanges 메서드를 호출합니다 context.SaveChanges.

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return new ObjectQuery<OfficeAssignment>("SELECT VALUE o FROM OfficeAssignments AS o", context).Include("Person").OrderBy("it." + sortExpression).ToList();
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.AddObject(officeAssignment);
    context.SaveChanges();
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    context.OfficeAssignments.Attach(officeAssignment);
    context.OfficeAssignments.DeleteObject(officeAssignment);
    context.SaveChanges();
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    context.OfficeAssignments.Attach(origOfficeAssignment);
    context.ApplyCurrentValues("OfficeAssignments", officeAssignment);
    SaveChanges();
}

테스트 프로젝트에서 MockSchoolRepository.cs 를 열고 다음 OfficeAssignment 컬렉션 및 CRUD 메서드를 추가합니다. 모의 리포지토리는 리포지토리 인터페이스를 구현해야 합니다. 그렇지 않으면 솔루션이 컴파일되지 않습니다.

List<OfficeAssignment> officeAssignments = new List<OfficeAssignment>();
        
public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    return officeAssignments;
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Add(officeAssignment);
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    officeAssignments.Remove(officeAssignment);
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    officeAssignments.Remove(origOfficeAssignment);
    officeAssignments.Add(officeAssignment);
}

BLL에 OfficeAssignment 메서드 추가

기본 프로젝트에서 SchoolBL.cs를 열고 엔터티 집합에 대해 OfficeAssignment 다음 CRUD 메서드를 추가합니다.

public IEnumerable<OfficeAssignment> GetOfficeAssignments(string sortExpression)
{
    if (string.IsNullOrEmpty(sortExpression)) sortExpression = "Person.LastName";
    return schoolRepository.GetOfficeAssignments(sortExpression);
}

public void InsertOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.InsertOfficeAssignment(officeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void DeleteOfficeAssignment(OfficeAssignment officeAssignment)
{
    try
    {
        schoolRepository.DeleteOfficeAssignment(officeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void UpdateOfficeAssignment(OfficeAssignment officeAssignment, OfficeAssignment origOfficeAssignment)
{
    try
    {
        schoolRepository.UpdateOfficeAssignment(officeAssignment, origOfficeAssignment);
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

OfficeAssignments 웹 페이지 만들기

Site.Master master 페이지를 사용하는 새 웹 페이지를 만들고 이름을 OfficeAssignments.aspx로 지정합니다. 라는 Content2컨트롤에 다음 태그를 Content 추가합니다.

<h2>Office Assignments</h2>
    <asp:ObjectDataSource ID="OfficeAssignmentsObjectDataSource" runat="server" TypeName="ContosoUniversity.BLL.SchoolBL"
        DataObjectTypeName="ContosoUniversity.DAL.OfficeAssignment" SelectMethod="GetOfficeAssignments"
        DeleteMethod="DeleteOfficeAssignment" UpdateMethod="UpdateOfficeAssignment" ConflictDetection="CompareAllValues"
        OldValuesParameterFormatString="orig{0}"
        SortParameterName="sortExpression"  OnUpdated="OfficeAssignmentsObjectDataSource_Updated">
    </asp:ObjectDataSource>
    <asp:ValidationSummary ID="OfficeAssignmentsValidationSummary" runat="server" ShowSummary="true"
        DisplayMode="BulletList" Style="color: Red; width: 40em;" />
    <asp:GridView ID="OfficeAssignmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="OfficeAssignmentsObjectDataSource" DataKeyNames="InstructorID,Timestamp"
        AllowSorting="True">
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" ItemStyle-VerticalAlign="Top">
                <ItemStyle VerticalAlign="Top"></ItemStyle>
            </asp:CommandField>
            <asp:TemplateField HeaderText="Instructor" SortExpression="Person.LastName">
                <ItemTemplate>
                    <asp:Label ID="InstructorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="InstructorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:DynamicField DataField="Location" HeaderText="Location" SortExpression="Location"/>
        </Columns>
        <SelectedRowStyle BackColor="LightGray"></SelectedRowStyle>
    </asp:GridView>

특성에서 DataKeyNames 태그는 속성과 레코드 키(InstructorID)를 지정합니다Timestamp. 특성에 DataKeyNames 속성을 지정하면 컨트롤이 포스트백 처리 중에 원래 값을 사용할 수 있도록 컨트롤 상태(보기 상태와 유사)에 저장됩니다.

값을 저장 Timestamp 하지 않은 경우 Entity Framework에는 SQL Update 명령의 절에 대한 값이 Where 없습니다. 따라서 업데이트할 항목이 없습니다. 결과적으로 Entity Framework는 엔터티가 업데이트될 때마다 낙관적 동시성 예외를 OfficeAssignment throw합니다.

OfficeAssignments.aspx.cs를 열고 데이터 액세스 계층에 대해 다음 using 문을 추가합니다.

using ContosoUniversity.DAL;

동적 데이터 기능을 사용하도록 설정하는 다음 Page_Init 메서드를 추가합니다. 또한 동시성 오류를 검사 ObjectDataSource 위해 컨트롤의 Updated 이벤트에 대해 다음 처리기를 추가합니다.

protected void Page_Init(object sender, EventArgs e)
{
    OfficeAssignmentsGridView.EnableDynamicData(typeof(OfficeAssignment));
}

protected void OfficeAssignmentsObjectDataSource_Updated(object sender, ObjectDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = "The record you attempted to " +
            "update has been modified by another user since you last visited this page. " +
            "Your update was canceled to allow you to review the other user's " +
            "changes and determine if you still want to update this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

OfficeAssignments 페이지에서 낙관적 동시성 테스트

OfficeAssignments.aspx 페이지를 실행합니다.

Office 과제 페이지를 보여 주는 스크린샷

행에서 편집 을 클릭하고 위치 열의 값을 변경합니다.

Image11

새 브라우저 창을 열고 페이지를 다시 실행합니다(첫 번째 브라우저 창에서 두 번째 브라우저 창으로 URL 복사).

새 브라우저 창을 보여 주는 스크린샷

이전에 편집한 행과 동일한 행에서 편집 을 클릭하고 위치 값을 다른 항목으로 변경합니다.

Image12

두 번째 브라우저 창에서 업데이트를 클릭합니다.

Image13

첫 번째 브라우저 창으로 전환하고 업데이트를 클릭합니다.

Image15

오류 메시지가 표시되고 위치 값이 업데이트되어 두 번째 브라우저 창에서 변경한 값을 표시합니다.

EntityDataSource 컨트롤을 사용하여 동시성 처리

컨트롤에는 EntityDataSource 데이터 모델의 동시성 설정을 인식하고 그에 따라 업데이트 및 삭제 작업을 처리하는 기본 제공 논리가 포함되어 있습니다. 그러나 모든 예외와 마찬가지로 사용자에게 친숙한 오류 메시지를 제공하려면 예외를 직접 처리 OptimisticConcurrencyException 해야 합니다.

다음으로, 업데이트 및 삭제 작업을 허용하고 동시성 충돌이 발생하는 경우 오류 메시지를 표시하도록 Courses.aspx 페이지(컨트롤 사용 EntityDataSource )를 구성합니다. Course 엔터티에 동시성 추적 열이 없으므로 엔터티와 Department 동일한 메서드를 사용합니다. 모든 비키 속성의 값을 추적합니다.

SchoolModel.edmx 파일을 엽니다. 엔터티(Title, CreditsDepartmentID)의 키가 아닌 속성의 Course 경우 동시성 모드 속성을 로 Fixed설정합니다. 그런 다음 데이터 모델을 저장하고 닫습니다.

Courses.aspx 페이지를 열고 다음을 변경합니다.

  • 컨트롤에서 CoursesEntityDataSourceEnableDelete="true" 특성을 추가 EnableUpdate="true" 합니다. 이제 해당 컨트롤의 여는 태그는 다음 예제와 유사합니다.

    <asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
            ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false" 
            AutoGenerateWhereClause="True" EntitySetName="Courses"
            EnableUpdate="true" EnableDelete="true">
    
  • 컨트롤에서 CoursesGridView 특성 값을 로 변경 DataKeyNames 합니다 "CourseID,Title,Credits,DepartmentID". 그런 다음, 편집삭제 단추(<asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />)를 표시하는 요소에 요소를 Columns 추가 CommandField 합니다. 이제 컨트롤은 GridView 다음 예제와 유사합니다.

    <asp:GridView ID="CoursesGridView" runat="server" AutoGenerateColumns="False" 
            DataKeyNames="CourseID,Title,Credits,DepartmentID"
            DataSourceID="CoursesEntityDataSource" >
            <Columns>
                <asp:CommandField ShowEditButton="True" ShowDeleteButton="True" />
                <asp:BoundField DataField="CourseID" HeaderText="CourseID" ReadOnly="True" SortExpression="CourseID" />
                <asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
                <asp:BoundField DataField="Credits" HeaderText="Credits" SortExpression="Credits" />
            </Columns>
        </asp:GridView>
    

페이지를 실행하고 부서 페이지에서 이전과 마찬가지로 충돌 상황을 만듭니다. 두 개의 브라우저 창에서 페이지를 실행하고, 각 창의 동일한 줄에서 편집 을 클릭하고, 각 창에서 다른 변경 내용을 만듭니다. 한 창에서 업데이트를 클릭한 다음 다른 창에서 업데이트를 클릭합니다. 두 번째로 업데이트를 클릭하면 처리되지 않은 동시성 예외로 인한 오류 페이지가 표시됩니다.

Image22

컨트롤에 대해 처리한 방법과 매우 유사한 방식으로 이 오류를 처리합니다 ObjectDataSource . Courses.aspx 페이지를 열고 컨트롤에서 CoursesEntityDataSourceUpdated 이벤트에 대한 Deleted 처리기를 지정합니다. 이제 컨트롤의 여는 태그는 다음 예제와 유사합니다.

<asp:EntityDataSource ID="CoursesEntityDataSource" runat="server" 
        ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="false"
        AutoGenerateWhereClause="true" EntitySetName="Courses" 
        EnableUpdate="true" EnableDelete="true" 
        OnDeleted="CoursesEntityDataSource_Deleted" 
        OnUpdated="CoursesEntityDataSource_Updated">

컨트롤 앞에 CoursesGridView 다음 컨트롤을 추가합니다.ValidationSummary

<asp:ValidationSummary ID="CoursesValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

Courses.aspx.cs에서 네임스페이스에 대한 System.Data 문을 추가하고using, 동시성 예외를 확인하는 메서드를 추가하고, 컨트롤 UpdatedDeleted 처리기에 대한 EntityDataSource 처리기를 추가합니다. 코드는 다음과 같습니다.

using System.Data;
protected void CoursesEntityDataSource_Updated(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "update");
}

protected void CoursesEntityDataSource_Deleted(object sender, EntityDataSourceChangedEventArgs e)
{
    CheckForOptimisticConcurrencyException(e, "delete");
}

private void CheckForOptimisticConcurrencyException(EntityDataSourceChangedEventArgs e, string function)
{
    if (e.Exception != null && e.Exception is OptimisticConcurrencyException)
    {
        var concurrencyExceptionValidator = new CustomValidator();
        concurrencyExceptionValidator.IsValid = false;
        concurrencyExceptionValidator.ErrorMessage = 
            "The record you attempted to edit or delete was modified by another " +
            "user after you got the original value. The edit or delete operation was canceled " +
            "and the other user's values have been displayed so you can " +
            "determine whether you still want to edit or delete this record.";
        Page.Validators.Add(concurrencyExceptionValidator);
        e.ExceptionHandled = true;
    }
}

이 코드와 컨트롤에 대해 ObjectDataSource 수행한 작업의 유일한 차이점은 이 경우 동시성 예외가 Exception 해당 예외의 속성이 아닌 이벤트 인수 개체의 InnerException 속성에 있다는 것입니다.

페이지를 실행하고 동시성 충돌을 다시 만듭니다. 이번에는 다음과 같은 오류 메시지가 표시됩니다.

Image23

동시성 충돌 처리에 대한 소개를 완료합니다. 다음 자습서에서는 Entity Framework를 사용하는 웹 애플리케이션의 성능을 개선하는 방법에 대한 지침을 제공합니다.