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

작성자 : 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은 프레젠테이션 계층을 데이터 액세스 계층과 분리하고 비즈니스 규칙을 적용합니다.

1단계: BLL 클래스 만들기

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

DAL 및 BLL 관련 클래스를 보다 명확하게 구분하려면 폴더 DALApp_CodeBLL하위 폴더 2개를 만들어 보겠습니다. 솔루션 탐색기 폴더를 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.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

[System.ComponentModel.DataObject]
public class ProductsBLL
{
    private ProductsTableAdapter _productsAdapter = null;
    protected ProductsTableAdapter Adapter
    {
        get {
            if (_productsAdapter == null)
                _productsAdapter = new ProductsTableAdapter();

            return _productsAdapter;
        }
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
    public Northwind.ProductsDataTable GetProducts()
    {
        return Adapter.GetProducts();
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductByProductID(int productID)
    {
        return Adapter.GetProductByProductID(productID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
    {
        return Adapter.GetProductsByCategoryID(categoryID);
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsBySupplierID(int supplierID)
    {
        return Adapter.GetProductsBySupplierID(supplierID);
    }
    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Insert, true)]
    public bool AddProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice,  short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued)
    {
        // Create a new ProductRow instance
        Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
        Northwind.ProductsRow product = products.NewProductsRow();

        product.ProductName = productName;
        if (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Add the new product
        products.AddProductsRow(product);
        int rowsAffected = Adapter.Update(products);

        // Return true if precisely one row was inserted,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Update, true)]
    public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
        string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
        short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
    {
        Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
        if (products.Count == 0)
            // no matching record found, return false
            return false;

        Northwind.ProductsRow product = products[0];

        product.ProductName = productName;
        if (supplierID == null) product.SetSupplierIDNull();
          else product.SupplierID = supplierID.Value;
        if (categoryID == null) product.SetCategoryIDNull();
          else product.CategoryID = categoryID.Value;
        if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
          else product.QuantityPerUnit = quantityPerUnit;
        if (unitPrice == null) product.SetUnitPriceNull();
          else product.UnitPrice = unitPrice.Value;
        if (unitsInStock == null) product.SetUnitsInStockNull();
          else product.UnitsInStock = unitsInStock.Value;
        if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
          else product.UnitsOnOrder = unitsOnOrder.Value;
        if (reorderLevel == null) product.SetReorderLevelNull();
          else product.ReorderLevel = reorderLevel.Value;
        product.Discontinued = discontinued;

        // Update the product record
        int rowsAffected = Adapter.Update(product);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }

    [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
    public bool DeleteProduct(int productID)
    {
        int rowsAffected = Adapter.Delete(productID);

        // Return true if precisely one row was deleted,
        // otherwise false
        return rowsAffected == 1;
    }
}

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

UpdateProduct 메서드는 AddProduct 모두 다양한 제품 필드의 값을 매개 변수로 사용하고 새 제품을 추가하거나 기존 제품을 각각 업데이트합니다. 테이블의 많은 열이 Product 값(CategoryID, SupplierIDUnitPrice)을 수락 NULL 하여 몇 가지 이름을 지정할 수 있으므로 이러한 열에 매핑되는 및 UpdateProduct 에 대한 AddProduct 입력 매개 변수는 nullable 형식을 사용합니다. Nullable 형식은 .NET 2.0에 새로 추가되었으며 값 형식이 이 null어야 하는지 여부를 나타내는 기술을 제공합니다. C#에서는 형식(예int? x;: ) 다음에 를 추가하여 ? 값 형식을 nullable 형식으로 플래그를 지정할 수 있습니다. 자세한 내용은 C# 프로그래밍 가이드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로 ObjectDataSource 업데이트ObjectDataSource 문제 및 Strongly-Typed DataSet을 참조하세요.

다음으로, 및 UpdateProduct모두에서 AddProduct 코드는 instance 만들고 방금 전달된 값으로 채웁니다 ProductsRow . 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 . 이 메서드는 공급자의 주소 정보만 업데이트하기 위한 인터페이스를 제공합니다. 내부적으로 이 메서드는 지정된 supplierID 에 대한 개체에서 SupplierDataRow 를 읽고(를 사용하여GetSupplierBySupplierID) 주소 관련 속성을 설정한 다음, 를 의 Update 메서드로 SupplierDataTable호출합니다. 메서드는 UpdateSupplierAddress 다음과 같습니다.

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateSupplierAddress
    (int supplierID, string address, string city, string country)
{
    Northwind.SuppliersDataTable suppliers =
        Adapter.GetSupplierBySupplierID(supplierID);
    if (suppliers.Count == 0)
        // no matching record found, return false
        return false;
    else
    {
        Northwind.SuppliersRow supplier = suppliers[0];

        if (address == null) supplier.SetAddressNull();
          else supplier.Address = address;
        if (city == null) supplier.SetCityNull();
          else supplier.City = city;
        if (country == null) supplier.SetCountryNull();
          else supplier.Country = country;

        // Update the supplier Address-related information
        int rowsAffected = Adapter.Update(supplier);

        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }
}

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

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

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

ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();

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

ProductsBLL productLogic = 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.cs폴더에 클래스를 App_Code 만듭니다.

App_Code 폴더에 새 클래스 추가

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

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

ProductsDataTable.ColumnChanging.cs

public partial class Northwind
{
    public partial class ProductsDataTable
    {
        public override void BeginInit()
         {
            this.ColumnChanging += ValidateColumn;
         }

         void ValidateColumn(object sender,
           DataColumnChangeEventArgs e)
         {
            if(e.Column.Equals(this.UnitPriceColumn))
            {
               if(!Convert.IsDBNull(e.ProposedValue) &&
                  (decimal)e.ProposedValue < 0)
               {
                  throw new ArgumentException(
                      "UnitPrice cannot be less than zero", "UnitPrice");
               }
            }
            else if (e.Column.Equals(this.UnitsInStockColumn) ||
                     e.Column.Equals(this.UnitsOnOrderColumn) ||
                     e.Column.Equals(this.ReorderLevelColumn))
            {
                if (!Convert.IsDBNull(e.ProposedValue) &&
                    (short)e.ProposedValue < 0)
                {
                    throw new ArgumentException(string.Format(
                        "{0} cannot be less than zero", e.Column.ColumnName),
                        e.Column.ColumnName);
                }
            }
         }
    }
}

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

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

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

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

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

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

public bool UpdateProduct(string productName, int? supplierID, int? categoryID,
    string quantityPerUnit, decimal? unitPrice, short? unitsInStock,
    short? unitsOnOrder, short? reorderLevel, bool discontinued, int productID)
{
    Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;

    Northwind.ProductsRow product = products[0];

    // Business rule check - cannot discontinue
    // a product that is supplied by only
    // one supplier
    if (discontinued)
    {
        // Get the products we buy from this supplier
        Northwind.ProductsDataTable productsBySupplier =
            Adapter.GetProductsBySupplierID(product.SupplierID);

        if (productsBySupplier.Count == 1)
            // this is the only product we buy from this supplier
            throw new ApplicationException(
                "You cannot mark a product as discontinued if it is the only
                  product purchased from a supplier");
    }

    product.ProductName = productName;
    if (supplierID == null) product.SetSupplierIDNull();
      else product.SupplierID = supplierID.Value;
    if (categoryID == null) product.SetCategoryIDNull();
      else product.CategoryID = categoryID.Value;
    if (quantityPerUnit == null) product.SetQuantityPerUnitNull();
      else product.QuantityPerUnit = quantityPerUnit;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null) product.SetUnitsInStockNull();
      else product.UnitsInStock = unitsInStock.Value;
    if (unitsOnOrder == null) product.SetUnitsOnOrderNull();
      else product.UnitsOnOrder = unitsOnOrder.Value;
    if (reorderLevel == null) product.SetReorderLevelNull();
      else product.ReorderLevel = reorderLevel.Value;
    product.Discontinued = discontinued;

    // Update the product record
    int rowsAffected = Adapter.Update(product);

    // Return true if precisely one row was updated,
    // otherwise false
    return rowsAffected == 1;
}

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

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

ProductsBLL productLogic = new ProductsBLL();

// Update information for ProductID 1
try
{
    // This will fail since we are attempting to use a
    // UnitPrice value less than 0.
    productLogic.UpdateProduct(
        "Scott s Tea", 1, 1, null, -14m, 10, null, null, false, 1);
}
catch (ArgumentException ae)
{
    Response.Write("There was a problem: " + ae.Message);
}

이후 자습서에서 볼 수 있듯이 데이터 웹 컨트롤을 사용하여 데이터를 삽입, 업데이트 또는 삭제할 때 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.