다음을 통해 공유


SqlDataSource를 사용하여 낙관적 동시성 구현(C#)

작성자 스콧 미첼

PDF 다운로드

이 자습서에서는 낙관적 동시성 제어의 필수 사항을 검토한 다음 SqlDataSource 컨트롤을 사용하여 구현하는 방법을 살펴봅니다.

소개

이전 자습서에서는 SqlDataSource 컨트롤에 삽입, 업데이트 및 삭제 기능을 추가하는 방법을 검토했습니다. 요컨대, 이러한 기능을 제공하기 위해 컨트롤 속성의 해당 INSERT, UPDATE, 또는 DELETE SQL 문을 지정하고, InsertCommand, UpdateCommand, 그리고 DeleteCommand 컬렉션에 적절한 매개 변수를 설정해야 했습니다. 이러한 속성 및 컬렉션을 수동으로 지정할 수도 있지만, 데이터 원본 구성 마법사의 고급 버튼에는 INSERT 문에 기반하여 UPDATE, DELETE, 및 SELECT 문을 자동으로 생성하는 확인란이 제공됩니다.

고급 SQL 생성 옵션 대화 상자에는 생성 INSERT, UPDATE, 및 DELETE 문 확인란과 함께 옵티미스틱 동시성 사용 옵션이 포함되어 있습니다(그림 1 참조). 선택 시, 자동 생성된 WHEREUPDATE 문서의 DELETE 절이, 사용자가 데이터를 그리드에 마지막으로 로드한 이후 기본 데이터베이스 데이터에 변경이 없었을 경우에만 업데이트 또는 삭제를 수행하도록 수정됩니다.

고급 SQL 생성 옵션 대화 상자에서 낙관적 동시성 지원을 추가할 수 있습니다.

그림 1: 고급 SQL 생성 옵션 대화 상자에서 낙관적 동시성 지원을 추가할 수 있습니다.

낙관적 동시성 구현 자습서에서는 낙관적 동시성 제어의 기본 사항과 ObjectDataSource에 추가하는 방법을 살펴보했습니다. 이 자습서에서는 낙관적 동시성 제어의 필수 사항을 수정한 다음 SqlDataSource를 사용하여 구현하는 방법을 살펴봅니다.

낙관적 동시성 요약

동시에 여러 사용자가 동일한 데이터를 편집하거나 삭제할 수 있는 웹 애플리케이션의 경우 한 사용자가 실수로 다른 변경 내용을 덮어쓸 수 있습니다. 낙관적 동시성 구현 자습서에서 다음 예제를 제공했습니다.

Jisun과 Sam이라는 두 사용자가 모두 방문자가 GridView 컨트롤을 통해 제품을 업데이트하고 삭제할 수 있는 애플리케이션의 페이지를 방문했다고 상상해 보세요. 둘 다 동시에 Chai에 대한 편집 단추를 클릭합니다. Jisun은 제품 이름을 Chai Tea로 변경하고 업데이트 단추를 클릭합니다. 최종 결과는 UPDATE 데이터베이스로 전송되는 문장입니다. Jisun이 하나의 필드만 업데이트했음에도 불구하고 제품의 모든 업데이트 가능한 필드를 설정합니다.ProductName. 이 시점에서 데이터베이스에는 이 특정 제품에 대한 값으로 차이 티, 카테고리인 음료, 공급업체인 Exotic Liquids 등이 있습니다. 그러나 Sam의 GridView 화면에는 편집 가능한 GridView 행의 제품 이름이 Chai로 표시됩니다. Jisun의 변경 내용이 커밋된 후 몇 초 후에 Sam은 범주를 Condiments로 업데이트하고 업데이트를 클릭합니다. 이렇게 하면 UPDATE이 데이터베이스로 보내져 제품 이름을 'Chai'로 설정하고, CategoryID를 해당 Condiments 카테고리 ID로 지정하는 SQL 문이 실행됩니다. 제품 이름에 대한 Jisun의 변경 사항이 덮어쓰여졌습니다.

그림 2에서는 이러한 상호 작용을 보여 줍니다.

두 사용자가 동시에 레코드를 업데이트하는 경우 한 사용자가 다른 사용자를 덮어쓸 가능성이 있습니다.

그림 2: 두 사용자가 동시에 레코드를 업데이트할 때 한 사용자가 다른 사용자를 덮어쓸 가능성이 있습니다(전체 크기 이미지를 보려면 클릭).

이 시나리오가 전개되지 않도록 하려면 동시성 제어 의 형태를 구현해야 합니다. 낙관적 동시성 이 자습서의 초점은 때때로 동시성 충돌이 있을 수 있지만 대부분의 경우 이러한 충돌이 발생하지 않는다는 가정하에 작동합니다. 따라서 충돌이 발생하는 경우 낙관적 동시성 제어는 다른 사용자가 동일한 데이터를 수정했기 때문에 변경 내용을 저장할 수 없다는 것을 사용자에게 알릴 뿐입니다.

비고

동시성 충돌이 많거나 이러한 충돌을 견딜 수 없는 경우 비관적 동시성 제어를 대신 사용할 수 있습니다. 비관적 동시성 제어에 대한 보다 철저한 논의는 낙관적 동시성 구현 자습서를 다시 참조하세요.

낙관적 동시성 제어는 업데이트 또는 삭제되는 레코드가 업데이트 또는 삭제 프로세스가 시작될 때와 동일한 값을 가지도록 하여 작동합니다. 예를 들어 편집 가능한 GridView에서 편집 단추를 클릭하면 레코드 값이 데이터베이스에서 읽혀지고 TextBoxes 및 기타 웹 컨트롤에 표시됩니다. 이러한 원래 값은 GridView에 의해 저장됩니다. 나중에 사용자가 변경하고 업데이트 단추를 UPDATE 클릭한 후 사용된 문은 원래 값과 새 값을 고려하고 사용자가 편집하기 시작한 원래 값이 데이터베이스의 값과 동일한 경우에만 기본 데이터베이스 레코드를 업데이트해야 합니다. 그림 3에서는 이 이벤트 시퀀스를 보여 줍니다.

업데이트 또는 삭제가 성공하려면 원래 값이 현재 데이터베이스 값과 같아야 합니다.

그림 3: 업데이트 또는 삭제가 성공하려면 원래 값이 현재 데이터베이스 값과 같아야 합니다(전체 크기 이미지를 보려면 클릭).

낙관적 동시성을 구현하는 다양한 방법이 있습니다(다양한 옵션을 간략하게 살펴보려면 Peter A. Bromberg낙관적 동시성 업데이트 논리 참조). SqlDataSource와 데이터 액세스 계층에 사용되는 ADO.NET 형식화된 데이터 세트에서 사용되는 기법은 WHERE 절을 확장하여 모든 원본 값들에 대한 비교를 포함합니다. 예를 들어 다음 UPDATE 문은 현재 데이터베이스 값이 GridView에서 레코드를 업데이트할 때 원래 검색된 값과 같은 경우에만 제품의 이름과 가격을 업데이트합니다. 및 @ProductName 매개 변수는 @UnitPrice 사용자가 입력한 새 값을 포함하는 반면 @original_ProductName@original_UnitPrice 편집 단추를 클릭할 때 원래 GridView에 로드된 값을 포함합니다.

UPDATE Products SET
    ProductName = @ProductName,
    UnitPrice = @UnitPrice
WHERE
    ProductID = @original_ProductID AND
    ProductName = @original_ProductName AND
    UnitPrice = @original_UnitPrice

이 자습서에서 볼 수 있듯이 SqlDataSource를 사용하여 낙관적 동시성 제어를 사용하도록 설정하는 것은 확인란을 확인하는 것만큼 간단합니다.

1단계: 낙관적 동시성을 지원하는 SqlDataSource 만들기

먼저 OptimisticConcurrency.aspx 폴더에서 SqlDataSource 페이지를 엽니다. 도구 상자에서 디자이너로 SqlDataSource 컨트롤을 끌어서 해당 ID 속성을 ProductsDataSourceWithOptimisticConcurrency로 설정합니다. 그런 다음 컨트롤의 스마트 태그에서 데이터 원본 구성 링크를 클릭합니다. 마법사의 첫 번째 화면에서 NORTHWINDConnectionString와 작업하도록 선택하고 다음을 클릭합니다.

NORTHWINDConnectionString과 함께 작업할 것을 선택하십시오

그림 4: NORTHWINDConnectionString과(와) 함께 작업하기 선택 (전체 크기 이미지를 보려면 클릭)

이 예제에서는 사용자가 테이블을 편집할 수 있는 GridView를 Products 추가하겠습니다. 따라서 [문 선택 구성] 화면에서 드롭다운 목록에서 Products 테이블을 선택하고, ProductID, ProductName, UnitPrice, Discontinued 열을 그림 5와 같이 선택합니다.

Products 테이블에서 ProductID, ProductName, UnitPrice 및 불연속 열을 반환합니다.

그림 5: Products 테이블에서 , ProductID, ProductNameUnitPrice 열을 반환Discontinued합니다(전체 크기 이미지를 보려면 클릭).

열을 선택한 후 고급 단추를 클릭하여 고급 SQL 생성 옵션 대화 상자를 표시합니다. 생성 INSERT, UPDATE, 및 DELETE 문을 확인하고 낙관적 동시성 확인란을 선택한 후 확인 버튼을 클릭하세요 (스크린샷은 그림 1에서 확인할 수 있습니다). 다음을 클릭한 다음 마침을 클릭하여 마법사를 완료합니다.

데이터 원본 구성 마법사를 완료한 후 잠시 시간을 내어 결과 DeleteCommand 및 속성 및 UpdateCommandDeleteParametersUpdateParameters 컬렉션을 검사합니다. 이 작업을 수행하는 가장 쉬운 방법은 왼쪽 아래 모서리에 있는 원본 탭을 클릭하여 페이지의 선언적 구문을 확인하는 것입니다. 그곳에서 다음의 UpdateCommand 값을 찾게 될 것입니다.

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

컬렉션에 7개의 매개 변수가 있습니다.UpdateParameters

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
      ...
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
    ...
</asp:SqlDataSource>

마찬가지로 DeleteCommand 속성 및 DeleteParameters 컬렉션은 다음과 같이 표시됩니다.

DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ...>
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        ...
    </UpdateParameters>
    ...
</asp:SqlDataSource>

낙관적 동시성 사용 옵션을 선택하면, WHERE 절에 UpdateCommandDeleteCommand 속성을 추가하고 각 매개 변수 컬렉션에 추가 매개 변수를 더하는 것 외에, 두 가지 다른 속성이 조정됩니다.

데이터 웹 컨트롤이 SqlDataSource Update() 또는 Delete() 메서드를 호출하면 원래 값이 전달됩니다. SqlDataSource의 ConflictDetection 속성을 설정 CompareAllValues하면 이러한 원래 값이 명령에 추가됩니다. 이 속성은 OldValuesParameterFormatString 이러한 원래 값 매개 변수에 사용되는 명명 패턴을 제공합니다. 데이터 원본 구성 마법사는 original_{0} 사용하며, 이에 따라 각 원본 매개 변수와 UpdateCommand 속성 및 DeleteCommandUpdateParameters 컬렉션의 DeleteParameters 이름을 지정합니다.

비고

SqlDataSource 컨트롤의 삽입 기능을 사용하지 않으므로, InsertCommand 속성과 InsertParameters 컬렉션을 자유롭게 제거하시면 됩니다.

값 올바르게 처리NULL

아쉽게도 낙관적 동시성을 사용할 때 데이터 원본 구성 마법사에서 자동으로 생성된 보강된 UPDATEDELETE 문은 값을 포함한 레코드에서는 NULL. 이유를 확인하려면 SqlDataSource UpdateCommand을(를) 고려해 보세요.

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     [UnitPrice] = @original_UnitPrice AND
     [Discontinued] = @original_Discontinued

UnitPrice 열은 Products 테이블에서 NULL 값을 가질 수 있습니다. 특정 레코드에 대한 NULLUnitPrice 값이 WHERE 있는 경우 절 부분은 [UnitPrice] = @original_UnitPrice항상 False를 반환하기 때문에 NULL = NULL 항상 False로 평가됩니다. 따라서 NULL 값을 포함하는 레코드는 UPDATE할 수 없으며, DELETEWHERE 문장의 절들이 업데이트하거나 삭제할 행을 반환하지 않기 때문입니다.

비고

이 버그는 2004년 6월 SqlDataSource에서 잘못된 SQL 문을 생성 하여 Microsoft에 처음 보고되었으며 다음 버전의 ASP.NET 수정될 예정입니다.

이 문제를 해결하려면, WHERE 값을 가질 수 있는 UpdateCommand 열의 DeleteCommand 속성에 있는 NULL 절을 수동으로 업데이트해야 합니다. 일반적으로 [ColumnName] = @original_ColumnName을 다음과 같이 변경합니다.

(
   ([ColumnName] IS NULL AND @original_ColumnName IS NULL)
     OR
   ([ColumnName] = @original_ColumnName)
)

이 수정은 선언적 태그를 통해, 속성 창의 UpdateQuery 또는 DeleteQuery 옵션을 통해 또는 데이터 원본 구성 마법사의 사용자 지정 SQL 문 또는 저장 프로시저 지정 옵션의 UPDATE 및 DELETE 탭을 통해 직접 수행할 수 있습니다. 다시 말하지만, 값을 포함 할 수 있는 and UpdateCommand s 절의 DeleteCommandWHERE 열에 NULL 대해 이 수정이 이루어져야 합니다.

예제에 이를 적용하면 다음과 같은 수정 UpdateCommand 된 값과 DeleteCommand 값이 생성됩니다.

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued
DELETE FROM [Products]
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
        OR ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

2단계: 편집 및 삭제 옵션을 사용하여 GridView 추가

낙관적 동시성을 지원하도록 구성된 SqlDataSource를 사용하면 이 동시성 컨트롤을 활용하는 페이지에 데이터 웹 컨트롤을 추가할 수 있습니다. 이 자습서에서는 편집 및 삭제 기능을 모두 제공하는 GridView를 추가해 보겠습니다. 이렇게 하려면 도구 상자에서 GridView를 디자이너로 끌어서 대상 IDProducts로 설정합니다. GridView의 스마트 태그에서 1단계에 추가된 ProductsDataSourceWithOptimisticConcurrency SqlDataSource 컨트롤에 바인딩합니다. 마지막으로 스마트 태그에서 편집 사용 및 삭제 사용 옵션을 선택합니다.

GridView를 SqlDataSource에 바인딩하고 편집 및 삭제 사용

그림 6: GridView를 SqlDataSource에 바인딩하고 편집 및 삭제 사용(전체 크기 이미지를 보려면 클릭)

GridView를 추가한 후 BoundField를 ProductID 제거하고, BoundField 속성을 ProductName Product로 변경 HeaderText 하고, 해당 속성이 단순히 Price가 되도록 BoundField를 UnitPrice 업데이트 HeaderText 하여 모양을 구성합니다. 편집 인터페이스를 개선하여 ProductName 값에 대한 RequiredFieldValidator와 UnitPrice 값이 올바르게 형식화된 숫자 값인지 확인하기 위한 CompareValidator를 포함하는 것이 이상적입니다. GridView의 편집 인터페이스 사용자 지정에 대한 자세한 내용은 데이터 수정 인터페이스 사용자 지정 자습서를 참조하세요.

비고

GridView에서 SqlDataSource로 전달된 원래 값이 뷰 상태에 저장되므로 GridView의 뷰 상태를 사용하도록 설정해야 합니다.

GridView를 수정한 후 GridView 및 SqlDataSource 선언적 태그는 다음과 유사하게 표시됩니다.

<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
    runat="server" ConflictDetection="CompareAllValues"
    ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
    DeleteCommand=
        "DELETE FROM [Products]
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
              OR ([UnitPrice] = @original_UnitPrice))
         AND [Discontinued] = @original_Discontinued"
    OldValuesParameterFormatString=
        "original_{0}"
    SelectCommand=
        "SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
         FROM [Products]"
    UpdateCommand=
        "UPDATE [Products]
         SET [ProductName] = @ProductName, [UnitPrice] = @UnitPrice,
            [Discontinued] = @Discontinued
         WHERE [ProductID] = @original_ProductID
         AND [ProductName] = @original_ProductName
         AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
            OR ([UnitPrice] = @original_UnitPrice))
        AND [Discontinued] = @original_Discontinued">
    <DeleteParameters>
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </DeleteParameters>
    <UpdateParameters>
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="UnitPrice" Type="Decimal" />
        <asp:Parameter Name="Discontinued" Type="Boolean" />
        <asp:Parameter Name="original_ProductID" Type="Int32" />
        <asp:Parameter Name="original_ProductName" Type="String" />
        <asp:Parameter Name="original_UnitPrice" Type="Decimal" />
        <asp:Parameter Name="original_Discontinued" Type="Boolean" />
    </UpdateParameters>
</asp:SqlDataSource>
<asp:GridView ID="Products" runat="server"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ProductsDataSourceWithOptimisticConcurrency">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Price"
            SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>

작동 중인 낙관적 동시성 컨트롤을 보려면 브라우저 창을 두 개 열고 두 브라우저 모두에서 페이지 OptimisticConcurrency.aspx를 로드하십시오. 두 브라우저에서 첫 번째 제품의 편집 단추를 클릭합니다. 한 브라우저에서 제품 이름을 변경하고 업데이트를 클릭합니다. 브라우저가 포스트백되고 GridView가 편집 전 모드로 돌아가 방금 편집한 레코드의 새 제품 이름을 표시합니다.

두 번째 브라우저 창에서 가격을 변경하고(제품 이름을 원래 값으로 유지) 업데이트를 클릭합니다. 포스트백 시 그리드는 사전 편집 모드로 반환되지만 가격 변경 내용은 기록되지 않습니다. 두 번째 브라우저는 새 제품 이름과 동일한 값을 이전 가격으로 표시합니다. 두 번째 브라우저 창에서 변경한 내용이 손실되었습니다. 또한 동시성 위반이 방금 발생했음을 나타내는 예외나 메시지가 없으므로 변경 내용이 다소 조용히 손실되었습니다.

두 번째 브라우저 창의 변경 내용이 자동으로 손실되었습니다.

그림 7: 두 번째 브라우저 창의 변경 내용이 자동으로 손실되었습니다(전체 크기 이미지를 보려면 클릭).

두 번째 브라우저의 변경 내용이 커밋되지 않은 이유는 UPDATE 문장의 WHERE 절이 모든 레코드를 필터링하여 어떤 행에도 영향을 미치지 않았기 때문입니다. 문장 UPDATE을 다시 살펴보겠습니다.

UPDATE [Products] SET
     [ProductName] = @ProductName,
     [UnitPrice] = @UnitPrice,
     [Discontinued] = @Discontinued
WHERE
     [ProductID] = @original_ProductID AND
     [ProductName] = @original_ProductName AND
     (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL) OR
        ([UnitPrice] = @original_UnitPrice)) AND
     [Discontinued] = @original_Discontinued

두 번째 브라우저 창에서 레코드를 업데이트하면 절에 WHERE 지정된 원래 제품 이름이 기존 제품 이름과 일치하지 않습니다(첫 번째 브라우저에서 변경했기 때문에). 따라서 문 [ProductName] = @original_ProductName 은 False를 반환하며 레코드에는 UPDATE 영향을 주지 않습니다.

비고

삭제는 동일한 방식으로 작동합니다. 두 개의 브라우저 창이 열려 있는 상태에서 먼저 지정된 제품을 편집한 다음 변경 내용을 저장합니다. 한 브라우저에서 변경 내용을 저장한 후 다른 브라우저에서 동일한 제품에 대한 삭제 단추를 클릭합니다. 원래 값이 DELETEWHERE 절에서 일치하지 않아서 삭제가 조용히 실패합니다.

두 번째 브라우저 창의 최종 사용자 관점에서, 업데이트 단추를 클릭한 후 그리드가 사전 편집 모드로 반환되지만 변경 내용이 손실되었습니다. 그러나 변경 내용이 달라지지 않았다는 시각적 피드백은 없습니다. 사용자의 변경 내용이 동시성 위반으로 손실되는 경우 사용자에게 알리고 그리드를 편집 모드로 유지하는 것이 가장 좋습니다. 이 작업을 수행하는 방법을 살펴보겠습니다.

3단계: 동시성 위반이 발생한 시기 확인

동시성 위반은 변경 내용을 거부하므로 동시성 위반이 발생했을 때 사용자에게 경고하는 것이 좋습니다. 사용자에게 경고하려면 속성에 다음 메시지가 표시되는 페이지 맨 ConcurrencyViolationMessageText 위에 레이블 웹 컨트롤을 추가해 보겠습니다. 다른 사용자가 동시에 업데이트한 레코드를 업데이트하거나 삭제하려고 했습니다. 다른 사용자의 변경 내용을 검토한 다음 업데이트를 다시 실행하거나 삭제하세요. 레이블 컨트롤의 CssClass 속성을 경고로 설정합니다. 이 클래스는 텍스트를 빨간색, 기울임꼴, 굵게 및 큰 글꼴로 표시하는 CSS 클래스 Styles.css 입니다. 마지막으로 레이블 VisibleEnableViewState 속성을 false.로 설정합니다. 이렇게 하면 해당 속성을 Visible으로 명시적으로 설정한 포스트백을 제외하고 레이블이 true 숨겨지게 됩니다.

페이지에 레이블 컨트롤을 추가하여 경고 표시

그림 8: 페이지에 레이블 컨트롤을 추가하여 경고 표시(전체 크기 이미지를 보려면 클릭)

업데이트 또는 삭제를 수행할 때, 데이터 소스 제어가 요청된 업데이트 또는 삭제를 수행한 후에, GridView의 RowUpdatedRowDeleted 이벤트 처리기가 실행됩니다. 이러한 이벤트 처리기의 작업에 의해 영향을 받은 행 수를 확인할 수 있습니다. 0개의 행이 영향을 받은 경우 레이블을 ConcurrencyViolationMessage 표시하려고 합니다.

RowUpdated 이벤트와 RowDeleted 이벤트 모두에 대한 이벤트 처리기를 만들고 다음 코드를 추가합니다.

protected void Products_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    if (e.AffectedRows == 0)
    {
        ConcurrencyViolationMessage.Visible = true;
        e.KeepInEditMode = true;
        // Rebind the data to the GridView to show the latest changes
        Products.DataBind();
    }
}
protected void Products_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    if (e.AffectedRows == 0)
        ConcurrencyViolationMessage.Visible = true;
}

두 이벤트 처리기에서 e.AffectedRows 속성을 확인하고, 0이면 ConcurrencyViolationMessage Label의 Visible 속성을 true으로 설정합니다. RowUpdated 이벤트 처리기에서 KeepInEditMode 속성을 true로 설정하여 GridView가 편집 모드를 유지하도록 지시합니다. 이렇게 하려면 다른 사용자의 데이터가 편집 인터페이스에 로드되도록 데이터를 그리드에 다시 바인딩해야 합니다. 이 작업은 GridView의 DataBind() 메서드를 호출하여 수행됩니다.

그림 9에서 알 수 있듯이 이러한 두 이벤트 처리기를 사용하면 동시성 위반이 발생할 때마다 매우 눈에 띄는 메시지가 표시됩니다.

동시성 위반이 발생할 때 메시지가 표시됩니다.

그림 9: 동시성 위반의 얼굴에 메시지가 표시됩니다(전체 크기 이미지를 보려면 클릭).

요약

여러 동시 사용자가 동일한 데이터를 편집할 수 있는 웹 애플리케이션을 만들 때 동시성 제어 옵션을 고려하는 것이 중요합니다. 기본적으로 ASP.NET 데이터 웹 컨트롤 및 데이터 원본 컨트롤은 동시성 제어를 사용하지 않습니다. 이 자습서에서 보았듯이 SqlDataSource를 사용하여 낙관적 동시성 제어를 구현하는 것은 비교적 빠르고 쉽습니다. SqlDataSource는 자동 생성된 WHEREUPDATE 문에 개선된 DELETE 절을 추가하는 대부분의 작업을 처리하지만, 값 열을 NULL 로 다루는 데 있어서는 "값을 정확하게 처리" 섹션에서 설명된 대로 몇 가지 미묘한 차이가 있습니다.

이 자습서에서는 SqlDataSource에 대한 검사를 완료합니다. 나머지 자습서는 ObjectDataSource 및 계층화된 아키텍처를 사용하여 데이터 작업으로 돌아갑니다.

행복한 프로그래밍!

작성자 정보

7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 연구해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 샘스 티치 유어셀프 ASP.NET 2.0 24시간 안에 배우기입니다. 그는 mitchell@4GuysFromRolla.com로 연락할 수 있습니다.