Поделиться через


Создание уровня бизнес-логики (VB)

Скотт Митчелл

Скачать в формате PDF

В этом руководстве мы посмотрим, как централизировать бизнес-правила на уровне бизнес-логики (BLL), который служит посредником для обмена данными между уровнем презентации и DAL.

Введение

Уровень доступа к данным (DAL), созданный в первом руководстве , четко отделяет логику доступа к данным от логики презентации. Однако, хотя DAL чисто отделяет сведения о доступе к данным от слоя презентации, он не применяет какие-либо бизнес-правила, которые могут применяться. Например, для нашего приложения может потребоваться запретить изменение полей CategoryID или SupplierID в таблице Products, если для поля Discontinued задано значение 1, или нам может потребоваться применить правила иерархии, запрещая ситуации, когда сотрудником управляет тот, кто был нанят позже него. Другой распространенный сценарий — авторизация, возможно, только пользователи определенной роли могут удалять продукты или изменять UnitPrice значение.

В этом руководстве мы посмотрим, как централизировать эти бизнес-правила в уровне бизнес-логики (BLL), который служит посредником для обмена данными между уровнем презентации и DAL. В реальном приложении BLL следует реализовать как отдельный проект библиотеки классов; Однако для этих учебников мы реализуем BLL в виде ряда классов в нашей App_Code папке, чтобы упростить структуру проекта. Рисунок 1 иллюстрирует архитектурные связи между уровнем презентации, BLL и DAL.

BLL отделяет уровень презентации от уровня доступа к данным и накладывает бизнес-правила

Рис. 1. BLL отделяет уровень презентации от уровня доступа к данным и накладывает бизнес-правила

Вместо создания отдельных классов для реализации бизнес-логики можно также поместить эту логику непосредственно в типизированный набор данных с частичными классами. Пример создания и расширения типизированного набора данных см. в первом руководстве.

Шаг 1. Создание классов BLL

Наш BLL будет состоять из четырех классов, по одному для каждого TableAdapter в DAL; каждый из этих классов BLL будет иметь методы для получения, вставки, обновления и удаления из соответствующего TableAdapter в DAL, применяя соответствующие бизнес-правила.

Чтобы более четко разделить классы, связанные с DAL и BLL, давайте создадим две вложенные папки в папке App_CodeDAL и BLL. Щелкните правой кнопкой мыши App_Code папку в обозревателе решений и выберите команду "Создать папку". После создания этих двух папок переместите типизированный набор данных, созданный в первом руководстве, в вложенную папку DAL .

Затем создайте четыре файла класса BLL в подпапке BLL . Чтобы это сделать, щелкните правой кнопкой мыши на BLL подпапке, выберите "Добавить новый элемент" и выберите шаблон класса. Назовите четыре класса ProductsBLL, CategoriesBLLи SuppliersBLLEmployeesBLL.

Добавление четырех новых классов в папку App_Code

Рис. 2. Добавление четырех новых классов в папку App_Code

Далее давайте добавим методы к каждому из классов, чтобы просто упаковать методы, определенные для TableAdapters из первого руководства. Сейчас эти методы будут просто вызываться непосредственно в DAL; мы позже добавим необходимую бизнес-логику.

Замечание

Если вы используете Visual Studio Standard Edition или более поздней версии (т. е. вы не используете Visual Web Developer), вы можете при необходимости визуально разработать классы с помощью конструктора классов. Дополнительные сведения об этой новой функции в Visual Studio см. в блоге конструктора классов .

ProductsBLL Для класса необходимо добавить в общей сложности семь методов:

  • GetProducts() возвращает все продукты
  • GetProductByProductID(productID) возвращает продукт с указанным идентификатором продукта
  • 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.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

Методы, которые просто возвращают данные GetProducts, GetProductByProductID, GetProductsByCategoryID и GetProductBySuppliersID, довольно просты, так как они просто обращаются к DAL. Хотя в некоторых сценариях могут быть бизнес-правила, которые должны быть реализованы на этом уровне (например, правила авторизации на основе текущего пользователя или роли, к которой принадлежит пользователь), мы просто оставим эти методы as-is. Для этих методов BLL служит лишь прокси-сервером, через который слой презентации обращается к базовым данным из уровня доступа к данным.

Методы AddProduct и UpdateProduct принимают в качестве параметров значения для различных полей продукта и соответственно добавляют новый продукт или обновляют существующий. Так как многие из Product столбцов таблицы могут принимать NULL значения (CategoryID, SupplierID и UnitPrice, чтобы назвать несколько), те входные параметры для AddProduct и UpdateProduct, которые соответствуют таким столбцам, используют типы, допускающие значение NULL. Типы, допускающие значение NULL, появились в .NET 2.0 и предоставляют способ указать, должен ли тип значения вместо этого быть Nothing. Дополнительные сведения см. в записи блога Пола ВикаThe Truth About Nullable Types and VB и в технической документации по структуре Nullable.

Все три метода возвращают логическое значение, указывающее, была ли строка вставлена, обновлена или удалена, так как операция не может привести к затронутой строке. Например, если разработчик страницы вызывает DeleteProduct, передавая ProductID для несуществующего продукта, DELETE SQL-запрос, отправленный в базу данных, не повлияет, и поэтому метод DeleteProduct вернет False.

Заметьте, что при добавлении нового продукта или обновлении существующего мы принимаем значения полей нового или измененного продукта в виде списка скаляров, а не экземпляра ProductsRow. Этот подход был выбран, так как ProductsRow класс является производным от класса ADO.NET DataRow , который не имеет конструктора без параметров по умолчанию. Чтобы создать новый экземпляр ProductsRow, сначала необходимо создать экземпляр ProductsDataTable, а затем вызвать его метод NewProductRow() (что мы делаем в AddProduct). Этот недостаток проявляется при вставке и обновлении товаров с помощью ObjectDataSource. Короче говоря, ObjectDataSource попытается создать экземпляр входных параметров. Если метод BLL ожидает экземпляр ProductsRow, то объект ObjectDataSource попытается создать его, но не сможет из-за отсутствия конструктора без параметров. Дополнительные сведения об этой проблеме см. в следующих двух ASP.NET форумах: обновление ObjectDataSources с помощью наборов данных Strongly-Typedи проблемы с ObjectDataSource и Strongly-Typed DataSet.

Затем в обоих AddProductUpdateProductслучаях код создает ProductsRow экземпляр и заполняет его только что переданными значениями. При назначении значений DataColumns в DataRow могут выполняться различные проверки на уровне поля. Поэтому ручное размещение переданных значений в DataRow помогает обеспечить допустимость передаваемых данных методу BLL. К сожалению, строго типизированные классы DataRow, созданные Visual Studio, не используют типы, допускающие значение NULL. Чтобы указать, что определенный столбец DataColumn в DataRow должен соответствовать 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 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

В первом руководстве мы видели примеры работы непосредственно с Типизированным набором данных программным способом, но при добавлении классов BLL уровень презентации должен работать с BLL вместо этого. AllProducts.aspx В примере из первого руководства ProductsTableAdapter использовался для привязки списка продуктов к GridView, как показано в следующем коде:

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

Чтобы использовать новые классы BLL, все, что необходимо изменить, является первой строкой кода, просто замените ProductsTableAdapter объект объектом ProductBLL :

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

Классы BLL также могут быть доступны декларативно (как и типизированный набор данных) с помощью ObjectDataSource. Мы обсудим ObjectDataSource более подробно в рамках следующих руководств.

Список продуктов отображается в GridView

Рис. 3. Список продуктов отображается в GridView (щелкните, чтобы просмотреть изображение полного размера)

Шаг 3. Добавление проверки Field-Level в классы DataRow

Проверка на уровне поля — это проверки, относящиеся к значениям свойств бизнес-объектов при вставке или обновлении. Ниже перечислены некоторые правила проверки на уровне поля для продуктов:

  • Поле ProductName должно содержать 40 символов или меньше длины.
  • Поле QuantityPerUnit должно содержать 20 символов или меньше длины.
  • Обязательными являются поля ProductID, ProductName и Discontinued, но все остальные поля необязательны.
  • Поля UnitPrice, UnitsInStock, UnitsOnOrder и ReorderLevel должны быть больше или равно нулю

Эти правила могут быть выражены на уровне базы данных. Ограничение символов для ProductNameQuantityPerUnit полей фиксируется типами данных этих столбцов в Products таблице (nvarchar(40) и nvarchar(20)соответственно). Указывает, являются ли поля обязательными и необязательными, если столбец таблицы базы данных допускает NULL s. Четыре ограничения существуют, чтобы гарантировать, что только значения, превышающие или равные нулю, могут попасть в столбцы UnitPrice, UnitsInStock, UnitsOnOrder или ReorderLevel.

Помимо применения этих правил в базе данных, они также должны быть применены на уровне Набора данных. Фактически, длина поля, а также требуемость или необязательность значения уже зафиксированы для каждого набора DataColumns таблицы данных (DataTable). Чтобы просмотреть автоматическую проверку на уровне поля, перейдите в конструктор наборов данных, выберите поле из одной из наборов данных, а затем перейдите в окно "Свойства". Как показано на рисунке 4, QuantityPerUnit DataColumn в ProductsDataTable имеет максимальную длину 20 символов и допускает NULL значения. Если мы попытаемся установить для ProductsDataRowQuantityPerUnit строковое значение длиной более 20 символов, будет выдано исключение ArgumentException.

DataColumn обеспечивает базовую проверку Field-Level

Рис. 4: DataColumn предоставляет базовую проверку Field-Level (нажмите, чтобы увеличить изображение)

К сожалению, невозможно указать проверки границ, например UnitPrice значение должно быть больше нуля или равно нулю в окне "Свойства". Чтобы предоставить этот тип проверки на уровне поля, необходимо создать обработчик событий для события ColumnChanging dataTable. Как упоминалось в предыдущем руководстве, объекты DataSet, DataTables и DataRow, созданные типизированным набором данных, можно расширить с помощью частичных классов. С помощью этого метода можно создать ColumnChanging обработчик событий для ProductsDataTable класса. Начните с создания класса в папке App_Code с именем ProductsDataTable.ColumnChanging.vb.

Добавление нового класса в папку App_Code

Рис. 5. Добавление нового класса в App_Code папку (щелкните, чтобы просмотреть изображение полного размера)

Затем создайте обработчик событий для события ColumnChanging, который гарантирует, что значения столбцов UnitPrice, UnitsInStock, UnitsOnOrder и ReorderLevel (если отсутствуют NULL) больше или равны нулю. Если любой такой столбец выходит за пределы диапазона, бросьте 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 как прекращенный; Если, однако, поставщик Y предоставил нам три продукта , A, B и C, то мы могли бы пометить все и все из них как прекращенные. Странное бизнес-правило, но бизнес-правила и здравый смысл не всегда совпадают!

Чтобы применить это бизнес-правило в методе UpdateProducts , мы начнем с проверки того, задано ли Discontinued значение True и, если да, мы будем вызывать GetProductsBySupplierID , чтобы определить, сколько продуктов мы приобрели у поставщика этого продукта. Если у этого поставщика приобретается только один продукт, мы бросаем 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.

Сводка

Хорошо архитекторированное приложение создается на отдельных уровнях, каждый из которых инкапсулирует определенную роль. В первом руководстве этой серии статей мы создали уровень доступа к данным с помощью типизированных наборов данных; в этом руководстве мы построили уровень бизнес-логики в виде ряда классов в папке нашего приложения App_Code, которые обращаются к нашему DAL. BLL реализует логику уровня полей и бизнес-уровня для нашего приложения. Помимо создания отдельного BLL, как мы сделали в этом руководстве, еще один вариант — расширить методы TableAdapters с помощью частичных классов. Однако использование этого метода не позволяет нам перекрывать существующие методы и отделять наш DAL и наш BLL так же чисто, как описанный подход в этой статье.

После завершения DAL и BLL мы готовы начать работу на нашем уровне презентации. В следующем руководстве мы рассмотрим краткий переход из разделов доступа к данным и определим согласованный макет страницы для использования во всех руководствах.

Счастливое программирование!

Сведения о авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга — Sams Teach Yourself ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.

Особое спасибо кому

Эта серия учебников была проверена многими полезными рецензентами. Ведущими рецензентами этого учебника были Лиз Шулок, Деннис Паттерсон, Карлос Сантос и Хилтон Гизенау. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.