스콧 미첼 지음
이 자습서에서는 비즈니스 규칙을 프레젠테이션 계층과 DAL 간의 데이터 교환을 위한 중개자 역할을 하는 BLL(비즈니스 논리 계층)으로 중앙 집중화하는 방법을 살펴보겠습니다.
소개
첫 번째 자습서에서 만든 DAL(데이터 액세스 계층)은 데이터 액세스 논리를 프레젠테이션 논리와 완전히 분리합니다. 그러나 DAL은 프레젠테이션 계층에서 데이터 액세스 세부 정보를 완전히 분리하지만 적용할 수 있는 비즈니스 규칙을 적용하지는 않습니다. 예를 들어, 저희 애플리케이션의 경우 CategoryID
필드가 1로 설정될 때 SupplierID
테이블의 Products
또는 Discontinued
필드를 수정하지 못하도록 제한할 수 있으며, 연공서열 규칙을 적용하여 직원이 그들보다 나중에 고용된 사람이 관리하는 상황을 금지하려고 할 수도 있습니다. 또 다른 일반적인 시나리오는 특정 역할의 사용자만 제품을 삭제하거나 값을 변경할 수 있는 권한 부여입니다 UnitPrice
.
이 자습서에서는 이러한 비즈니스 규칙을 프레젠테이션 계층과 DAL 간의 데이터 교환을 위한 중개자 역할을 하는 BLL(비즈니스 논리 계층)으로 중앙 집중화하는 방법을 알아보세요. 실제 애플리케이션에서 BLL은 별도의 클래스 라이브러리 프로젝트로 구현되어야 합니다. 그러나 이러한 자습서에서는 프로젝트 구조를 간소화하기 위해 BLL을 폴더의 일련의 클래스 App_Code
로 구현합니다. 그림 1에서는 프레젠테이션 계층, BLL 및 DAL 간의 아키텍처 관계를 보여 줍니다.
그림 1: BLL은 프레젠테이션 계층을 데이터 액세스 계층과 분리하고 비즈니스 규칙을 적용합니다.
1단계: BLL 클래스 만들기
BLL은 DAL의 각 TableAdapter에 대해 하나씩 네 개의 클래스로 구성됩니다. 이러한 각 BLL 클래스에는 DAL의 해당 TableAdapter에서 검색, 삽입, 업데이트 및 삭제하고 적절한 비즈니스 규칙을 적용하는 메서드가 있습니다.
DAL 및 BLL 관련 클래스를 보다 명확하게 구분하려면 폴더 App_Code
DAL
에 BLL
두 개의 하위 폴더를 만들고 . 솔루션 탐색기에서 폴더를 App_Code
마우스 오른쪽 단추로 클릭하고 새 폴더를 선택하기만 하면 됩니다. 이 두 폴더를 만든 후 첫 번째 자습서에서 만든 Typed DataSet을 하위 폴더로 DAL
이동합니다.
다음으로 하위 폴더에 BLL
4개의 BLL 클래스 파일을 만듭니다. 이렇게 하려면 하위 폴더를 BLL
마우스 오른쪽 단추로 클릭하고 새 항목 추가를 선택한 다음 클래스 템플릿을 선택합니다. 네 개의 클래스 이름을 지정ProductsBLL
CategoriesBLL
SuppliersBLL
합니다.EmployeesBLL
그림 2: 폴더에 App_Code
4개의 새 클래스 추가
다음으로, 첫 번째 자습서에서 TableAdapters에 대해 정의된 메서드를 래핑하기 위해 각 클래스에 메서드를 추가해 보겠습니다. 지금은 이러한 메서드들이 DAL에 직접 호출됩니다. 필요한 비즈니스 논리를 추가하기 위해 나중에 다시 돌아올 것입니다.
비고
Visual Studio Standard Edition 이상을 사용하는 경우(즉, Visual Web Developer를 사용하지 않는 경우) 필요에 따라 클래스 디자이너를 사용하여 클래스를 시각적으로 디자인할 수 있습니다. Visual Studio의 이 새로운 기능에 대한 자세한 내용은 클래스 디자이너 블로그 를 참조하세요.
클래스의 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)
는 전달된 값을 사용하여 데이터베이스의 기존 제품을 업데이트합니다. 정확히 한 행이 업데이트되었으면 반환하고,true
그렇지 않으면 반환false
합니다. -
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
GetProductByProductID
GetProductsByCategoryID
반환하고 GetProductBySuppliersID
DAL을 호출하기만 하면 매우 간단한 메서드입니다. 일부 시나리오에서는 이 수준에서 구현해야 하는 비즈니스 규칙(예: 현재 로그온한 사용자 또는 사용자가 속한 역할을 기반으로 하는 권한 부여 규칙)이 있을 수 있지만 이러한 메서드는 as-is그대로 둡니다. 이러한 메서드의 경우 BLL은 프레젠테이션 계층이 데이터 액세스 계층의 기본 데이터에 액세스하는 프록시로만 사용됩니다.
및 AddProduct
메서드는 UpdateProduct
각각 다양한 제품 필드의 값을 매개 변수로 사용하고 새 제품을 추가하거나 기존 제품을 업데이트합니다. 테이블의 Product
열 중 많은 부분은 특정 값(NULL
, CategoryID
, 및 SupplierID
등)을 수락할 수 있으므로, 이러한 열에 매핑되는 UnitPrice
와 AddProduct
입력 매개 변수는 UpdateProduct
을 사용합니다. Nullable 형식은 .NET 2.0의 새로운 형식이며, 값 형식을 대신 사용해야 null
하는지 여부를 나타내는 기술을 제공합니다. C#에서 값 형식을 nullable 형식으로 플래그 지정하려면 형식 뒤에 ?
를 추가할 수 있습니다 (예: int? x;
). 자세한 내용은 C# 프로그래밍 가이드의 Nullable 형식 섹션을 참조하세요.
세 메서드는 모두 작업이 영향을 받는 행을 생성하지 않을 수 있으므로 행이 삽입, 업데이트 또는 삭제되었는지 여부를 나타내는 부울 값을 반환합니다. 예를 들어, 페이지 개발자가 존재하지 않는 제품에 대한 DeleteProduct
을 전달하여 ProductID
을 호출하면, 데이터베이스에 발행된 DELETE
구문은 아무런 영향을 미치지 않으며, 따라서 메서드는 DeleteProduct
을 반환하여 false
됩니다.
제품을 추가하거나 기존 제품을 업데이트할 때, ProductsRow
인스턴스를 수락하는 대신, 새로운 제품 또는 수정된 제품의 필드 값을 스칼라 목록으로 처리합니다. 이 방법은 클래스가 ProductsRow
기본 매개 변수 없는 생성자가 없는 ADO.NET DataRow
클래스에서 파생되기 때문에 선택되었습니다. 새 ProductsRow
인스턴스를 만들려면 먼저 인스턴스를 ProductsDataTable
만든 다음 메서드 NewProductRow()
(여기서 수행 AddProduct
)를 호출해야 합니다. 이 문제점은 ObjectDataSource를 사용하여 제품을 삽입하고 업데이트할 때 드러납니다. 간단히 말해서 ObjectDataSource는 입력 매개 변수의 인스턴스를 만들려고 합니다. BLL 메서드에 인스턴스가 ProductsRow
필요하면 ObjectDataSource는 인스턴스를 만들려고 하지만 기본 매개 변수가 없는 생성자가 없어 실패합니다. 이 문제에 대한 자세한 내용은 두 개의 ASP.NET 포럼 게시물을 참조하세요. Strongly-Typed DataSets로 ObjectDataSources 업데이트 및 ObjectDataSource 문제 및 Strongly-Typed DataSet.
다음으로, AddProduct
및 UpdateProduct
에서 코드가 ProductsRow
인스턴스를 생성하고 방금 전달된 값으로 채웁니다. DataRow의 DataColumns에 값을 할당할 때 다양한 필드 수준 유효성 검사가 발생할 수 있습니다. 따라서 전달된 값을 DataRow에 수동으로 다시 배치하면 BLL 메서드에 전달되는 데이터의 유효성을 보장할 수 있습니다. 안타깝게도 Visual Studio에서 생성된 강력한 형식의 DataRow 클래스는 nullable 형식을 사용하지 않습니다. 특정 DataRow의 DataColumn이 NULL
데이터베이스 값에 해당해야 함을 나타내기 위해서는 SetColumnNameNull()
메서드를 사용해야 합니다.
UpdateProduct
에서 업데이트할 제품을 먼저 로드하고, GetProductByProductID(productID)
를 사용하여 업데이트합니다. 이 작업은 데이터베이스에 대한 불필요한 이동처럼 보일 수 있지만, 이 추가 여행은 낙관적 동시성을 탐색하는 향후 자습서에서 가치가 있음을 증명합니다. 낙관적 동시성은 동일한 데이터에서 동시에 작업하는 두 사용자가 실수로 서로의 변경 내용을 덮어쓰지 않도록 하는 기술입니다. 또한 전체 레코드를 사용하면 BLL에서 DataRow 열의 하위 집합만 수정하는 업데이트 메서드를 쉽게 만들 수 있습니다. 클래스를 SuppliersBLL
탐색할 때 이러한 예제를 볼 수 있습니다.
마지막으로 클래스에 ProductsBLL
DataObject 특성 이 적용되고( [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)
주목할 만한 한 가지 메서드는 클래스의 메서드입니다 SuppliersBLL
UpdateSupplierAddress
. 이 메서드는 공급자의 주소 정보만 업데이트하기 위한 인터페이스를 제공합니다. 내부적으로 이 메서드는 SupplierDataRow
을 사용하여 지정된 supplierID
에 대한 GetSupplierBySupplierID
개체를 읽고, 그 주소 관련 속성을 설정한 다음, SupplierDataTable
의 Update
메서드를 호출합니다. 메서드는 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 클래스를 통해 형식화된 데이터 세트 액세스
첫 번째 자습서에서는 프로그래밍 방식으로 Typed DataSet을 직접 사용하는 예제를 보았지만 BLL 클래스를 추가하면 프레젠테이션 계층이 BLL에 대해 대신 작동해야 합니다.
AllProducts.aspx
첫 번째 자습서 ProductsTableAdapter
의 예제에서는 다음 코드와 같이 제품 목록을 GridView에 바인딩하는 데 사용되었습니다.
ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
새 BLL 클래스를 사용하려면, 코드의 첫 번째 줄에서 ProductsTableAdapter
개체를 ProductBLL
개체로 단순히 교체하기만 하면 됩니다.
ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();
또한 BLL 클래스는 ObjectDataSource를 사용하여 선언적으로(형식화된 데이터 세트와 마찬가지로) 액세스할 수 있습니다. 다음 자습서에서는 ObjectDataSource에 대해 자세히 설명합니다.
그림 3: 제품 목록이 GridView에 표시됩니다(전체 크기 이미지를 보려면 클릭).
3단계: DataRow 클래스에 Field-Level 유효성 검사 추가
필드 수준 유효성 검사는 삽입하거나 업데이트할 때 비즈니스 개체의 속성 값과 관련된 검사입니다. 제품에 대한 일부 필드 수준 유효성 검사 규칙은 다음과 같습니다.
-
ProductName
필드는 길이가 40자 이하여야 합니다. -
QuantityPerUnit
필드는 20자 이하이어야 합니다. -
ProductID
,ProductName
및Discontinued
필드가 필요하지만 다른 모든 필드는 선택 사항입니다. - ,
UnitPrice
및UnitsInStock
UnitsOnOrder
필드가ReorderLevel
0보다 크거나 같아야 합니다.
이러한 규칙은 데이터베이스 수준에서 표현될 수 있고 표현되어야 합니다.
ProductName
및 QuantityPerUnit
필드의 문자 제한은 Products
테이블의 해당 열 데이터 유형에 의해 결정됩니다(nvarchar(40)
및 nvarchar(20)
각각). 데이터베이스 테이블 열이 NULL
를 허용하는지 여부에 따라 필드가 필수인지 선택 사항인지가 결정됩니다.
체크 제약 조건 네 가지가 존재하여 0 이상인 값만 UnitPrice
, UnitsInStock
, UnitsOnOrder
, 또는 ReorderLevel
열에 들어갈 수 있도록 보장합니다.
데이터베이스에서 이러한 규칙을 적용하는 것 외에도 DataSet 수준에서 적용해야 합니다. 실제로 각 DataTable의 DataColumns 집합에 대해 필드 길이 및 값이 필요한지 또는 선택 사항인지 여부가 이미 캡처되어 있습니다. 자동으로 제공되는 기존 필드 수준 유효성 검사를 보려면 DataSet 디자이너로 이동하여 DataTable 중 하나에서 필드를 선택한 다음 속성 창으로 이동합니다. 그림 4에서 보이듯, QuantityPerUnit
의 DataColumn은 최대 길이가 20자이며 ProductsDataTable
값을 허용합니다.
ProductsDataRow
객체의 QuantityPerUnit
속성을 20자를 초과하는 문자열 값으로 설정하려고 하면 ArgumentException
예외가 발생합니다.
그림 4: DataColumn은 기본 Field-Level 유효성 검사를 제공합니다(전체 크기 이미지를 보려면 클릭).
아쉽게도 속성 창을 통해 값이 0보다 크거나 같아야 하는 것과 같은 UnitPrice
경계 검사를 지정할 수 없습니다. 이러한 유형의 필드 수준 유효성 검사를 제공하려면 DataTable의 ColumnChanging 이벤트에 대한 이벤트 처리기를 만들어야 합니다.
이전 자습서에서 설명한 것처럼 Typed DataSet에서 만든 DataSet, DataTables 및 DataRow 개체는 부분 클래스를 사용하여 확장할 수 있습니다. 이 기술을 사용하여 클래스에 ColumnChanging
대한 ProductsDataTable
이벤트 처리기를 만들 수 있습니다.
App_Code
폴더에 ProductsDataTable.ColumnChanging.cs
라는 이름의 클래스를 생성합니다.
그림 5: 폴더에 App_Code
새 클래스 추가(전체 크기 이미지를 보려면 클릭)
다음으로, ColumnChanging
이벤트에 대한 이벤트 처리기를 만들어서 UnitPrice
, UnitsInStock
, UnitsOnOrder
, 및 ReorderLevel
열 값이 (만약 NULL
가 아닌 경우) 0보다 크거나 같은지 확인합니다. 이러한 열이 범위를 벗어날 경우, 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 를 중단된 것으로 표시할 수 없습니다. 그러나 공급업체 Y 가 A, B 및 C라는 세 가지 제품을 제공했으면 이러한 모든 제품을 중단된 것으로 표시할 수 있습니다. 이상한 비즈니스 규칙이지만 비즈니스 규칙과 상식이 항상 정렬되지는 않습니다!
이 비즈니스 규칙을 UpdateProducts
메서드에 적용하려면 먼저 Discontinued
가 true
로 설정되었는지 확인하고, 그렇다면, 이 제품의 공급업체에서 구매한 제품 수를 결정하기 위해 GetProductsBySupplierID
을 호출합니다. 이 공급업체에서 단 하나의 제품만 구매한 경우, 우리는 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으로 넘겨서 HttpApplication
Error
이벤트를 발생시킬지 결정할 수 있습니다. 예외를 처리하기 위해 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이 완료되면 프레젠테이션 계층에서 시작할 준비가 된 것입니다. 다음 자습서에서는 데이터 액세스 항목에서 잠시 우회하고 자습서 전체에서 사용할 일관된 페이지 레이아웃을 정의합니다.
행복한 프로그래밍!
작성자 정보
7개의 ASP/ASP.NET 책의 저자이자 4GuysFromRolla.com 창립자인 Scott Mitchell은 1998년부터 Microsoft 웹 기술을 연구해 왔습니다. Scott은 독립 컨설턴트, 트레이너 및 작가로 일합니다. 그의 최신 책은 Sams 시리즈의 'Teach Yourself ASP.NET 2.0 in 24 Hours'입니다. 그는 mitchell@4GuysFromRolla.com로 연락할 수 있습니다.
특별히 감사드립니다.
이 자습서 시리즈는 많은 유용한 검토자가 검토했습니다. 이 자습서의 수석 검토자는 Liz Shulok, Dennis Patterson, Carlos Santos 및 Hilton Giesenow였습니다. 예정된 MSDN 문서를 검토하는 데 관심이 있으신가요? 그렇다면 mitchell@4GuysFromRolla.com으로 메시지를 보내 주세요.