비즈니스 논리 레이어 만들기(VB)

작성자 : Scott Mitchell

PDF 다운로드

이 자습서에서는 비즈니스 규칙을 프레젠테이션 계층과 DAL 간의 데이터 교환을 위한 중개자 역할을 하는 BLL(비즈니스 논리 계층)으로 중앙 집중화하는 방법을 알아보세요.

소개

첫 번째 자습서에서 만든 DAL(데이터 액세스 계층)은 데이터 액세스 논리를 프레젠테이션 논리와 깔끔하게 구분합니다. 그러나 DAL은 프레젠테이션 계층에서 데이터 액세스 세부 정보를 깔끔하게 분리하지만 적용할 수 있는 비즈니스 규칙을 적용하지는 않습니다. 예를 들어 애플리케이션의 경우 필드가 1로 설정된 경우 Discontinued 테이블의 Products 또는 SupplierID 필드를 수정하지 못하게 CategoryID 하거나 연공서열 규칙을 적용하여 직원이 고용된 사람이 관리하는 상황을 금지할 수 있습니다. 또 다른 일반적인 시나리오는 특정 역할의 사용자만 제품을 삭제하거나 값을 변경할 수 있는 권한 부여입니다 UnitPrice .

이 자습서에서는 이러한 비즈니스 규칙을 프레젠테이션 계층과 DAL 간의 데이터 교환을 위한 중개자 역할을 하는 BLL(비즈니스 논리 계층)으로 중앙 집중화하는 방법을 알아보세요. 실제 애플리케이션에서 BLL은 별도의 클래스 라이브러리 프로젝트로 구현되어야 합니다. 그러나 이러한 자습서에서는 프로젝트 구조를 간소화하기 위해 BLL을 App_Code 폴더의 일련의 클래스로 구현합니다. 그림 1에서는 프레젠테이션 계층, BLL 및 DAL 간의 아키텍처 관계를 보여 줍니다.

BLL은 프레젠테이션 계층을 데이터 액세스 계층과 분리하고 비즈니스 규칙을 적용합니다.

그림 1: BLL은 프레젠테이션 계층을 데이터 액세스 계층과 분리하고 비즈니스 규칙을 적용합니다.

비즈니스 논리를 구현하기 위해 별도의 클래스를 만드는 대신 부분 클래스가 있는 Typed DataSet에 이 논리를 직접 배치할 수 있습니다. Typed DataSet을 만들고 확장하는 예제는 첫 번째 자습서를 참조하세요.

1단계: BLL 클래스 만들기

BLL은 DAL의 각 TableAdapter에 대해 하나씩 네 개의 클래스로 구성됩니다. 이러한 각 BLL 클래스에는 적절한 비즈니스 규칙을 적용하여 DAL의 각 TableAdapter에서 검색, 삽입, 업데이트 및 삭제하는 메서드가 있습니다.

DAL 및 BLL 관련 클래스를 보다 명확하게 구분하려면 폴더 DAL 에 두 개의 하위 폴더 및 BLLApp_Code 만들어 보겠습니다. 솔루션 탐색기 폴더를 App_Code 마우스 오른쪽 단추로 클릭하고 새 폴더를 선택합니다. 이 두 폴더를 만든 후 첫 번째 자습서에서 만든 형식화된 데이터 세트를 하위 폴더로 DAL 이동합니다.

다음으로, 하위 폴더에 BLL 4개의 BLL 클래스 파일을 만듭니다. 이렇게 하려면 하위 폴더를 BLL 마우스 오른쪽 단추로 클릭하고 새 항목 추가를 선택하고 클래스 템플릿을 선택합니다. 네 클래스의 이름을 , CategoriesBLL, SuppliersBLLEmployeesBLL로 지정합니다ProductsBLL.

App_Code 폴더에 새 클래스 4개 추가

그림 2: 폴더에 App_Code 새 클래스 4개 추가

다음으로, 첫 번째 자습서에서 TableAdapters에 대해 정의된 메서드를 래핑하기 위해 각 클래스에 메서드를 추가해 보겠습니다. 지금은 이러한 메서드가 DAL에 직접 호출됩니다. 나중에 반환하여 필요한 비즈니스 논리를 추가합니다.

참고

Visual Studio Standard Edition 이상을 사용하는 경우(즉, Visual Web Developer를 사용하지 않는 경우) 필요에 따라 클래스 Designer 사용하여 클래스를 시각적으로 디자인할 수 있습니다. Visual Studio의 이 새로운 기능에 대한 자세한 내용은 클래스 Designer 블로그를 참조하세요.

클래스의 ProductsBLL 경우 총 7개의 메서드를 추가해야 합니다.

  • GetProducts() 는 모든 제품을 반환합니다.
  • GetProductByProductID(productID) 지정된 제품 ID를 사용하여 제품을 반환합니다.
  • GetProductsByCategoryID(categoryID) 는 지정된 범주의 모든 제품을 반환합니다.
  • GetProductsBySupplier(supplierID) 지정된 공급자의 모든 제품을 반환합니다.
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) 는 전달된 값을 사용하여 데이터베이스에 새 제품을 삽입합니다. 는 ProductID 새로 삽입된 레코드의 값을 반환합니다.
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)는 전달된 값을 사용하여 데이터베이스의 기존 제품을 업데이트합니다. 정확히 한 행이 업데이트되었으면 를 반환하고, False 그렇지 않으면 를 반환합니다True.
  • DeleteProduct(productID) 는 데이터베이스에서 지정된 제품을 삭제합니다.

ProductsBLL.vb

Imports NorthwindTableAdapters

<System.ComponentModel.DataObject()> _
Public Class ProductsBLL

    Private _productsAdapter As ProductsTableAdapter = Nothing
    Protected ReadOnly Property Adapter() As ProductsTableAdapter
        Get
            If _productsAdapter Is Nothing Then
                _productsAdapter = New ProductsTableAdapter()
            End If

            Return _productsAdapter
        End Get
    End Property

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As Northwind.ProductsDataTable
        Return Adapter.GetProducts()
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductByProductID(ByVal productID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductByProductID(productID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsByCategoryID(categoryID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Select, False)> _
    Public Function GetProductsBySupplierID(ByVal supplierID As Integer) _
        As Northwind.ProductsDataTable
        Return Adapter.GetProductsBySupplierID(supplierID)
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Insert, True)> _
    Public Function AddProduct( _
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean) _
        As Boolean

        Dim products As New Northwind.ProductsDataTable()
        Dim product As Northwind.ProductsRow = products.NewProductsRow()

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        products.AddProductsRow(product)
        Dim rowsAffected As Integer = Adapter.Update(products)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(_
        productName As String, supplierID As Nullable(Of Integer), _
        categoryID As Nullable(Of Integer), quantityPerUnit As String, _
        unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
        unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
        discontinued As Boolean, productID As Integer) _
        As Boolean

        Dim products As Northwind.ProductsDataTable = _
            Adapter.GetProductByProductID(productID)

        If products.Count = 0 Then
            Return False
        End If

        Dim product as Northwind.ProductsRow = products(0)

        product.ProductName = productName
        If Not supplierID.HasValue Then
            product.SetSupplierIDNull()
        Else
            product.SupplierID = supplierID.Value
        End If

        If Not categoryID.HasValue Then
            product.SetCategoryIDNull()
        Else
            product.CategoryID = categoryID.Value
        End If

        If quantityPerUnit Is Nothing Then
            product.SetQuantityPerUnitNull()
        Else
            product.QuantityPerUnit = quantityPerUnit
        End If

        If Not unitPrice.HasValue Then
            product.SetUnitPriceNull()
        Else
            product.UnitPrice = unitPrice.Value
        End If

        If Not unitsInStock.HasValue Then
            product.SetUnitsInStockNull()
        Else
            product.UnitsInStock = unitsInStock.Value
        End If

        If Not unitsOnOrder.HasValue Then
            product.SetUnitsOnOrderNull()
        Else
            product.UnitsOnOrder = unitsOnOrder.Value
        End If

        If Not reorderLevel.HasValue Then
            product.SetReorderLevelNull()
        Else
            product.ReorderLevel = reorderLevel.Value
        End If

        product.Discontinued = discontinued

        Dim rowsAffected As Integer = Adapter.Update(product)

        Return rowsAffected = 1
    End Function

    <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, True)> _
    Public Function DeleteProduct(ByVal productID As Integer) As Boolean
        Dim rowsAffected As Integer = Adapter.Delete(productID)

        Return rowsAffected = 1
    End Function
End Class

단순히 데이터 , , GetProductByProductIDGetProductsByCategoryIDGetProductBySuppliersIDGetProducts반환하는 메서드는 DAL로 호출하기만 하면 매우 간단합니다. 일부 시나리오에서는 이 수준에서 구현해야 하는 비즈니스 규칙(예: 현재 로그온한 사용자 또는 사용자가 속한 역할에 기반한 권한 부여 규칙)이 있을 수 있지만 이러한 메서드는 그대로 둡니다. 이러한 메서드의 경우 BLL은 프레젠테이션 계층이 데이터 액세스 계층의 기본 데이터에 액세스하는 프록시 역할을 합니다.

UpdateProduct 메서드는 AddProduct 각각 다양한 제품 필드의 값을 매개 변수로 사용하고 새 제품을 추가하거나 기존 제품을 업데이트합니다. 테이블의 많은 열이 Product 값(CategoryID, SupplierID, 및 UnitPrice)을 수락 NULL 하여 몇 가지 이름을 지정할 수 있으므로 이러한 열에 매핑되는 및 UpdateProduct 에 대한 AddProduct 입력 매개 변수는 nullable 형식을 사용합니다. Nullable 형식은 .NET 2.0에 새로 추가되었으며 값 형식 Nothing이 이어야 하는지 여부를 나타내는 기술을 제공합니다. 자세한 내용은 Paul Vick의 블로그 항목 Nullable 형식 및 VB에 대한 진실 및Nullable 구조에 대한 기술 설명서를 참조하세요.

세 메서드는 모두 연산으로 인해 영향을 받는 행이 발생하지 않을 수 있으므로 행이 삽입, 업데이트 또는 삭제되었는지 여부를 나타내는 부울 값을 반환합니다. 예를 들어 페이지 개발자가 존재하지 않는 제품에 DELETE 대해 를 전달 ProductID 하도록 호출 DeleteProduct 하는 경우 데이터베이스에 발급된 문은 영향을 주지 않으므로 메서드는 DeleteProduct 를 반환False합니다.

새 제품을 추가하거나 기존 제품을 업데이트할 때는 instance 수락하는 ProductsRow 대신 새 제품 또는 수정된 제품의 필드 값을 스칼라 목록으로 사용합니다. 이 방법은 클래스가 ProductsRow 기본 매개 변수 없는 생성자가 없는 ADO.NET DataRow 클래스에서 파생되기 때문에 선택되었습니다. 새 ProductsRow instance 만들려면 먼저 instance 만든 ProductsDataTable 다음 메서드(에서 수행AddProduct)를 NewProductRow() 호출해야 합니다. 이 단점은 ObjectDataSource를 사용하여 제품을 삽입하고 업데이트할 때 머리를 뒤로 돌립니다. 즉, ObjectDataSource는 입력 매개 변수의 instance 만들려고 합니다. BLL 메서드에 instance 필요한 ProductsRow 경우 ObjectDataSource는 생성을 시도하지만 기본 매개 변수가 없는 생성자가 없기 때문에 실패합니다. 이 문제에 대한 자세한 내용은 다음 두 ASP.NET 포럼 게시물인 Strongly-Typed DataSets를 사용하여 ObjectDataSources 업데이트ObjectDataSource 문제 및 Strongly-Typed DataSet을 참조하세요.

다음으로, 및 UpdateProduct모두에서 AddProduct 코드는 ProductsRow instance 만들고 방금 전달된 값으로 채웁니다. DataRow의 DataColumns에 값을 할당할 때 다양한 필드 수준 유효성 검사가 발생할 수 있습니다. 따라서 전달된 값을 DataRow에 수동으로 다시 넣으면 BLL 메서드에 전달되는 데이터의 유효성을 확인할 수 있습니다. 안타깝게도 Visual Studio에서 생성된 강력한 형식의 DataRow 클래스는 nullable 형식을 사용하지 않습니다. 대신 DataRow의 특정 DataColumn이 데이터베이스 값에 해당해야 함을 NULL 나타내려면 메서드를 SetColumnNameNull() 사용해야 합니다.

먼저 UpdateProduct 을 사용하여 GetProductByProductID(productID)업데이트하기 위해 제품을 로드합니다. 데이터베이스를 불필요하게 여행하는 것처럼 보일 수 있지만, 이 추가 여행은 낙관적 동시성을 탐색하는 향후 자습서에서 가치 있는 것으로 입증될 것입니다. 낙관적 동시성은 동일한 데이터에서 동시에 작업하는 두 사용자가 실수로 서로의 변경 내용을 덮어쓰지 않도록 하는 기술입니다. 또한 전체 레코드를 사용하면 DataRow 열의 하위 집합만 수정하는 업데이트 메서드를 BLL에서 쉽게 만들 수 있습니다. 클래스를 SuppliersBLL 탐색할 때 이러한 예제가 표시됩니다.

마지막으로 클래스에 ProductsBLLDataObject 특성 이 적용되어 있고( [System.ComponentModel.DataObject] 파일 맨 위에 있는 클래스 문 바로 앞의 구문) 메서드에는 DataObjectMethodAttribute 특성이 있습니다. 특성은 DataObject 클래스를 ObjectDataSource 컨트롤에 바인딩하는 데 적합한 개체로 표시하는 반면 은 DataObjectMethodAttribute 메서드의 용도를 나타냅니다. 이후 자습서에서 볼 수 있듯이 ASP.NET 2.0의 ObjectDataSource를 사용하면 클래스의 데이터에 쉽게 액세스할 수 있습니다. ObjectDataSource의 마법사에서 바인딩할 가능한 클래스 목록을 필터링하려면 기본적으로 로 DataObjects 표시된 클래스만 마법사의 드롭다운 목록에 표시됩니다. 클래스는 ProductsBLL 이러한 특성 없이도 작동하지만 추가하면 ObjectDataSource의 마법사에서 더 쉽게 작업할 수 있습니다.

기타 클래스 추가

클래스가 ProductsBLL 완료되면 범주, 공급자 및 직원 작업을 위한 클래스를 추가해야 합니다. 잠시 시간을 내어 위의 예제의 개념을 사용하여 다음 클래스와 메서드를 만듭니다.

  • CategoriesBLL.cs

    • GetCategories()
    • GetCategoryByCategoryID(categoryID)
  • SuppliersBLL.cs

    • GetSuppliers()
    • GetSupplierBySupplierID(supplierID)
    • GetSuppliersByCountry(country)
    • UpdateSupplierAddress(supplierID, address, city, country)
  • EmployeesBLL.cs

    • GetEmployees()
    • GetEmployeeByEmployeeID(employeeID)
    • GetEmployeesByManager(managerID)

주목할 만한 한 가지 메서드는 클래스의 메서드입니다 SuppliersBLLUpdateSupplierAddress . 이 메서드는 공급자의 주소 정보만 업데이트하기 위한 인터페이스를 제공합니다. 내부적으로 이 메서드는 지정된 (사용GetSupplierBySupplierID)에 대한 개체에서 SupplierDataRow 를 읽고 주소 관련 속성을 설정한 다음 를 의 Update 메서드로 SupplierDataTable호출합니다.supplierID 메서드는 UpdateSupplierAddress 다음과 같습니다.

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateSupplierAddress(ByVal supplierID As Integer, _
    ByVal address As String, ByVal city As String, ByVal country As String) _
    As Boolean

    Dim suppliers As Northwind.SuppliersDataTable = _
        Adapter.GetSupplierBySupplierID(supplierID)

    If suppliers.Count = 0 Then
        Return False
    Else
        Dim supplier As Northwind.SuppliersRow = suppliers(0)

        If address Is Nothing Then
            supplier.SetAddressNull()
        Else
            supplier.Address = address
        End If

        If city Is Nothing Then
            supplier.SetCityNull()
        Else
            supplier.City = city
        End If

        If country Is Nothing Then
            supplier.SetCountryNull()
        Else
            supplier.Country = country
        End If

        Dim rowsAffected As Integer = Adapter.Update(supplier)

        Return rowsAffected = 1
    End If
End Function

BLL 클래스의 전체 구현은 이 문서의 다운로드를 참조하세요.

2단계: BLL 클래스를 통해 형식화된 데이터 세트 액세스

첫 번째 자습서에서는 형식화된 DataSet을 프로그래밍 방식으로 직접 사용하는 예제를 보았지만 BLL 클래스를 추가하면 프레젠테이션 계층이 BLL에 대해 대신 작동해야 합니다. 첫 번째 자습서 ProductsTableAdapterAllProducts.aspx 예제에서 은 다음 코드와 같이 제품 목록을 GridView에 바인딩하는 데 사용되었습니다.

Dim productsAdapter As New ProductsTableAdapter()
GridView1.DataSource = productsAdapter.GetProducts()
GridView1.DataBind()

새 BLL 클래스를 사용하려면 변경해야 하는 코드의 첫 번째 줄만 개체로 ProductBLL 바꿉 ProductsTableAdapter 니다.

Dim productLogic As New ProductsBLL()
GridView1.DataSource = productLogic.GetProducts()
GridView1.DataBind()

BLL 클래스는 ObjectDataSource를 사용하여 선언적으로(형식화된 DataSet과 마찬가지로) 액세스할 수도 있습니다. 다음 자습서에서는 ObjectDataSource에 대해 자세히 설명합니다.

GridView에 제품 목록이 표시됩니다.

그림 3: 제품 목록이 GridView에 표시됩니다(전체 크기 이미지를 보려면 클릭).

3단계: DataRow 클래스에 Field-Level 유효성 검사 추가

필드 수준 유효성 검사는 삽입하거나 업데이트할 때 비즈니스 개체의 속성 값과 관련된 검사입니다. 제품에 대한 일부 필드 수준 유효성 검사 규칙은 다음과 같습니다.

  • ProductName 필드는 길이가 40자 이하여야 합니다.
  • QuantityPerUnit 필드는 길이가 20자 이하여야 합니다.
  • ProductID, ProductNameDiscontinued 필드가 필요하지만 다른 모든 필드는 선택 사항입니다.
  • UnitPrice, UnitsInStock, UnitsOnOrderReorderLevel 필드는 0보다 크거나 같아야 합니다.

이러한 규칙은 데이터베이스 수준에서 표현될 수 있고 표현되어야 합니다. 및 필드의 ProductName 문자 제한은 테이블(nvarchar(40)nvarchar(20)각각)에 있는 해당 열의 데이터 형식에 Products 의해 캡처 QuantityPerUnit 됩니다. 데이터베이스 테이블 열에서 를 허용하는 NULL 경우 필드가 필요한지 여부와 선택 사항이 로 표현되는지 여부입니다. 0보다 크거나 같은 값만 , , UnitsInStockUnitsOnOrder또는 ReorderLevel 열로 UnitPrice만들 수 있도록 하는 네 가지 검사 제약 조건이 있습니다.

데이터베이스에서 이러한 규칙을 적용하는 것 외에도 DataSet 수준에서도 적용해야 합니다. 실제로 각 DataTable의 DataColumns 집합에 대해 필드 길이 및 값이 필요한지 또는 선택 사항인지 여부가 이미 캡처되어 있습니다. 자동으로 제공되는 기존 필드 수준 유효성 검사를 보려면 DataSet Designer 이동하여 DataTable 중 하나에서 필드를 선택한 다음, 속성 창 이동합니다. 그림 4에서 ProductsDataTableQuantityPerUnit 수 있듯이 의 DataColumn은 최대 길이가 20자이며 값을 허용 NULL 합니다. 의 QuantityPerUnit 속성을 20자보다 긴 문자열 값으로 설정ProductsDataRow하려고 하면 이 ArgumentException throw됩니다.

DataColumn은 기본 Field-Level 유효성 검사를 제공합니다.

그림 4: DataColumn은 기본 Field-Level 유효성 검사를 제공합니다(전체 크기 이미지를 보려면 클릭).

아쉽게도 속성 창 통해 값이 UnitPrice 0보다 크거나 같아야 하는 것과 같은 경계 검사를 지정할 수 없습니다. 이러한 유형의 필드 수준 유효성 검사를 제공하려면 DataTable의 ColumnChanging 이벤트에 대한 이벤트 처리기를 만들어야 합니다. 이전 자습서에서 설명한 대로 Typed DataSet에서 만든 DataSet, DataTables 및 DataRow 개체는 partial 클래스를 사용하여 확장할 수 있습니다. 이 기술을 사용하여 클래스에 ColumnChanging 대한 ProductsDataTable 이벤트 처리기를 만들 수 있습니다. 먼저 라는 ProductsDataTable.ColumnChanging.vb폴더에 클래스를 App_Code 만듭니다.

App_Code 폴더에 새 클래스 추가

그림 5: 폴더에 App_Code 새 클래스 추가(전체 크기 이미지를 보려면 클릭)

다음으로, , , UnitsInStockReorderLevelUnitsOnOrder열 값(그렇지 않은 NULL경우)이 0보다 크거나 같은지 확인하는 UnitPrice이벤트에 대한 ColumnChanging 이벤트 처리기를 만듭니다. 이러한 열이 범위를 벗어나면 을 throw합니다 ArgumentException.

ProductsDataTable.ColumnChanging.vb

Imports System.data

Partial Public Class Northwind
    Partial Public Class ProductsDataTable
        Public Overrides Sub BeginInit()
            AddHandler Me.ColumnChanging, AddressOf ValidateColumn
        End Sub

        Sub ValidateColumn(sender As Object, e As DataColumnChangeEventArgs)
            If e.Column.Equals(Me.UnitPriceColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Decimal) < 0 Then
                    Throw New ArgumentException( _
                        "UnitPrice cannot be less than zero", "UnitPrice")
                End If
            ElseIf e.Column.Equals(Me.UnitsInStockColumn) OrElse _
                e.Column.Equals(Me.UnitsOnOrderColumn) OrElse _
                e.Column.Equals(Me.ReorderLevelColumn) Then
                If Not Convert.IsDBNull(e.ProposedValue) AndAlso _
                    CType(e.ProposedValue, Short) < 0 Then
                    Throw New ArgumentException(String.Format( _
                        "{0} cannot be less than zero", e.Column.ColumnName), _
                        e.Column.ColumnName)
                End If
            End If
        End Sub
    End Class
End Class

4단계: BLL의 클래스에 사용자 지정 비즈니스 규칙 추가

필드 수준 유효성 검사 외에도 다음과 같이 단일 열 수준에서 표현할 수 없는 다른 엔터티 또는 개념을 포함하는 높은 수준의 사용자 지정 비즈니스 규칙이 있을 수 있습니다.

  • 제품이 중단되면 UnitPrice 업데이트할 수 없습니다.
  • 직원의 거주 국가는 관리자의 거주 국가와 동일해야 합니다.
  • 공급자가 제공하는 유일한 제품인 경우 제품을 중단할 수 없습니다.

BLL 클래스에는 애플리케이션의 비즈니스 규칙을 준수하는지 확인하는 검사가 포함되어야 합니다. 이러한 검사는 적용되는 메서드에 직접 추가할 수 있습니다.

비즈니스 규칙에 따라 지정된 공급업체의 유일한 제품인 경우 제품을 중단된 것으로 표시할 수 없다고 규정한다고 상상해 보십시오. 즉, 제품 X 가 공급업체 Y에서 구매한 유일한 제품인 경우 X 를 중단된 것으로 표시할 수 없습니다. 그러나 공급업체 YA, BC라는 세 가지 제품을 제공했으면 이러한 모든 제품을 중단된 것으로 표시할 수 있습니다. 이상한 비즈니스 규칙이지만 비즈니스 규칙과 상식이 항상 정렬되지는 않습니다!

먼저 이 비즈니스 규칙을 UpdateProducts 로 설정 True 했는지 Discontinued 확인하고, 설정된 경우 이 제품의 공급업체에서 구매한 제품 수를 확인하도록 호출 GetProductsBySupplierID 합니다. 이 공급업체에서 제품을 하나만 구매하면 을 throw합니다 ApplicationException.

<System.ComponentModel.DataObjectMethodAttribute_
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct( _
    productName As String, supplierID As Nullable(Of Integer), _
    categoryID As Nullable(Of Integer), quantityPerUnit As String, _
    unitPrice As Nullable(Of Decimal), unitsInStock As Nullable(Of Short), _
    unitsOnOrder As Nullable(Of Short), reorderLevel As Nullable(Of Short), _
    discontinued As Boolean, productID As Integer) _
    As Boolean

    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)

    If products.Count = 0 Then
        Return False
    End If

    Dim product As Northwind.ProductsRow = products(0)

    If discontinued Then
        Dim productsBySupplier As Northwind.ProductsDataTable = _
            Adapter.GetProductsBySupplierID(product.SupplierID)

        If productsBySupplier.Count = 1 Then
            Throw New ApplicationException( _
                "You cannot mark a product as discontinued if it is " & _
                "the only product purchased from a supplier")
        End If
    End If

    product.ProductName = productName

    If Not supplierID.HasValue Then
        product.SetSupplierIDNull()
    Else
        product.SupplierID = supplierID.Value
    End If

    If Not categoryID.HasValue Then
        product.SetCategoryIDNull()
    Else
        product.CategoryID = categoryID.Value
    End If

    If quantityPerUnit Is Nothing Then
        product.SetQuantityPerUnitNull()
    Else
        product.QuantityPerUnit = quantityPerUnit
    End If

    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If

    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If

    If Not unitsOnOrder.HasValue Then
        product.SetUnitsOnOrderNull()
    Else
        product.UnitsOnOrder = unitsOnOrder.Value
    End If

    If Not reorderLevel.HasValue Then
        product.SetReorderLevelNull()
    Else
        product.ReorderLevel = reorderLevel.Value
    End If

    product.Discontinued = discontinued

    Dim rowsAffected As Integer = Adapter.Update(product)

    Return rowsAffected = 1
End Function

프레젠테이션 계층의 유효성 검사 오류에 응답

프레젠테이션 계층에서 BLL을 호출할 때 발생할 수 있는 예외를 처리할지 아니면 ASP.NET(의 이벤트를 발생 HttpApplicationError 시킬 수 있음)까지 버블링할 것인지 결정할 수 있습니다. 프로그래밍 방식으로 BLL을 사용할 때 예외를 처리하기 위해 Try...를 사용할 수 있습니다. 다음 예제와 같이 Catch 블록:

Dim productLogic As New ProductsBLL()

Try
    productLogic.UpdateProduct("Scotts Tea", 1, 1, Nothing, _
      -14, 10, Nothing, Nothing, False, 1)
Catch ae As ArgumentException
    Response.Write("There was a problem: " & ae.Message)
End Try

이후 자습서에서 볼 수 있듯이 데이터 웹 컨트롤을 사용하여 데이터를 삽입, 업데이트 또는 삭제할 때 BLL에서 버블 업되는 예외를 처리하는 것은 코드를 블록으로 래핑하지 않고도 이벤트 처리기에서 Try...Catch 직접 처리할 수 있습니다.

요약

잘 설계된 애플리케이션은 각각 특정 역할을 캡슐화하는 고유한 계층으로 제작됩니다. 이 문서 시리즈의 첫 번째 자습서에서는 형식화된 데이터 세트를 사용하여 데이터 액세스 계층을 만들었습니다. 이 자습서에서는 DAL을 호출하는 애플리케이션의 App_Code 폴더에 일련의 클래스로 비즈니스 논리 계층을 빌드했습니다. BLL은 애플리케이션에 대한 필드 수준 및 비즈니스 수준 논리를 구현합니다. 이 자습서에서와 같이 별도의 BLL을 만드는 것 외에도 부분 클래스를 사용하여 TableAdapters의 메서드를 확장하는 것이 또 다른 옵션입니다. 그러나 이 기술을 사용하면 기존 메서드를 재정의할 수 없으며 이 문서에서 수행한 접근 방식처럼 DAL과 BLL을 깔끔하게 분리하지도 않습니다.

DAL 및 BLL이 완료되면 프레젠테이션 계층에서 시작할 준비가 된 것입니다. 다음 자습서에서는 데이터 액세스 topics 잠시 우회하고 자습서 전체에서 사용할 일관된 페이지 레이아웃을 정의합니다.

행복한 프로그래밍!

저자 정보

7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술로 작업해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 샘스 티치 유어셀프 ASP.NET 24시간 만에 2.0입니다. 그는 에서mitchell@4GuysFromRolla.com 또는 에서 찾을 http://ScottOnWriting.NET수있는 자신의 블로그를 통해 도달 할 수 있습니다.

특별 감사

이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 Liz Shulok, Dennis Patterson, Carlos Santos 및 Hilton Giesenow였습니다. 예정된 MSDN 문서를 검토하는 데 관심이 있으신가요? 그렇다면 에 줄을 놓습니다 mitchell@4GuysFromRolla.com.