Dela via


Skapa ett affärslogiklager (VB)

av Scott Mitchell

Ladda ned PDF

I den här självstudien får vi se hur du centraliserar dina affärsregler till ett BLL (Business Logic Layer) som fungerar som mellanhand för datautbyte mellan presentationslagret och DAL.

Inledning

Dataåtkomstskiktet (DAL) som skapades i den första självstudien separerar rent dataåtkomstlogiken från presentationslogiken. Men även om DAL separerar informationen om dataåtkomst från presentationslagret på ett rent sätt, framtvingar den inte några affärsregler som kan gälla. För vårt program kanske vi till exempel inte tillåter att fälten CategoryIDSupplierID i Products tabellen ändras när Discontinued fältet är inställt på 1, eller så kanske vi vill tillämpa senioritetsregler, vilket förbjuder situationer där en anställd hanteras av någon som anställdes efter dem. Ett annat vanligt scenario är auktorisering kanske bara användare i en viss roll kan ta bort produkter eller ändra UnitPrice värdet.

I den här självstudien får vi se hur du centraliserar dessa affärsregler till ett BLL (Business Logic Layer) som fungerar som mellanhand för datautbyte mellan presentationslagret och DAL. I ett verkligt program bör BLL implementeras som ett separat klassbiblioteksprojekt. För de här självstudierna implementerar vi dock BLL som en serie klasser i mappen App_Code för att förenkla projektstrukturen. Bild 1 illustrerar de arkitektoniska relationerna mellan presentationsskiktet, BLL och DAL.

BLL separerar presentationslagret från dataåtkomstskiktet och inför affärsregler

Bild 1: BLL separerar presentationslagret från dataåtkomstskiktet och inför affärsregler

I stället för att skapa separata klasser för att implementera vår affärslogik kan vi också placera den här logiken direkt i Typed DataSet med partiella klasser. Ett exempel på hur du skapar och utökar en Typed DataSet finns i den första självstudien.

Steg 1: Skapa BLL-klasserna

Vår BLL kommer att bestå av fyra klasser, en för varje TableAdapter i DAL; var och en av dessa BLL-klasser har metoder för att hämta, infoga, uppdatera och ta bort från respektive TableAdapter i DAL, med lämpliga affärsregler.

Om du vill separera dal- och BLL-relaterade klasser på ett mer rent sätt skapar vi två undermappar i App_Code mappen DAL och BLL. Högerklicka bara på App_Code mappen i Solution Explorer och välj Ny mapp. När du har skapat dessa två mappar, flytta det Typed DataSet som skapades i den första självstudien till undermappen DAL.

Skapa sedan de fyra BLL-klassfilerna i undermappen BLL . Det gör du genom att högerklicka på undermappen BLL , välja Lägg till ett nytt objekt och välja mallen Klass. Namnge de fyra klasserna ProductsBLL, CategoriesBLL, SuppliersBLLoch EmployeesBLL.

Lägg till fyra nya klasser i mappen App_Code

Bild 2: Lägg till fyra nya klasser i mappen App_Code

Nu ska vi lägga till metoder i var och en av klasserna för att helt enkelt omsluta de metoder som definierats för TableAdapters från den första självstudien. För tillfället anropar dessa metoder bara direkt till DAL; vi återkommer senare för att lägga till nödvändig affärslogik.

Anmärkning

Om du använder Visual Studio Standard Edition eller senare (dvs. du inte använder Visual Web Developer) kan du välja att utforma dina klasser visuellt med hjälp av klassdesignern. Mer information om den här nya funktionen i Visual Studio finns i Class Designer-bloggen .

För klassen ProductsBLL behöver vi lägga till totalt sju metoder:

  • GetProducts() returnerar alla produkter
  • GetProductByProductID(productID) returnerar produkten med angivet produkt-ID
  • GetProductsByCategoryID(categoryID) returnerar alla produkter från den angivna kategorin
  • GetProductsBySupplier(supplierID) returnerar alla produkter från den angivna leverantören
  • AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued) infogar en ny produkt i databasen med hjälp av de anförda värdena. returnerar värdet för ProductID den nyligen infogade posten
  • UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID) uppdaterar en befintlig produkt i databasen med hjälp av de införda värdena. returnerar True om exakt en rad har uppdaterats, False annars
  • DeleteProduct(productID) tar bort den angivna produkten från databasen

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

De metoder som helt enkelt returnerar data GetProducts, GetProductByProductID, GetProductsByCategoryIDoch GetProductBySuppliersID är ganska enkla eftersom de helt enkelt anropar ner till DAL. I vissa scenarier kan det finnas affärsregler som måste implementeras på den här nivån (till exempel auktoriseringsregler baserade på den användare som för närvarande är inloggad eller den roll som användaren tillhör), men vi lämnar helt enkelt dessa metoder as-is. För dessa metoder fungerar BLL bara som en proxy genom vilken presentationslagret kommer åt underliggande data från dataåtkomstlagret.

Metoderna AddProduct och UpdateProduct tar båda in som parametrar värdena för de olika produktfälten och lägger till en ny produkt eller uppdaterar en befintlig. Eftersom många av Product tabellens kolumner kan acceptera NULL värden (CategoryID, SupplierID, och UnitPrice, för att nämna några), använder dessa indataparametrar för AddProduct och UpdateProduct som mappar till sådana kolumner nullbara typer. Typer som kan vara null introducerades i .NET 2.0 och ger en teknik för att ange om en värdetyp i stället ska vara Nothing. Mer information finns i Paul Vicks blogginlägg The Truth About Nullable Types och VB och den tekniska dokumentationen för Nullable-strukturen .

Alla tre metoderna returnerar ett booleskt värde som anger om en rad infogades, uppdaterades eller togs bort eftersom åtgärden kanske inte resulterar i en påverkad rad. Om sidutvecklaren till exempel anropar DeleteProduct genom att skicka in en ProductID för en produkt som inte existerar, kommer instruktionen DELETE som utfärdas till databasen att ha ingen inverkan, och därför kommer metoden DeleteProduct att returnera False.

Observera att när vi lägger till en ny produkt eller uppdaterar en befintlig, tar vi in den nya eller ändrade produktens fältvärden som en lista över skalärer i stället för att acceptera en ProductsRow instans. Den här metoden valdes eftersom ProductsRow klassen härleds från klassen ADO.NET DataRow , som inte har någon standardkonstruktor utan parameter. För att kunna skapa en ny ProductsRow instans måste vi först skapa en ProductsDataTable instans och sedan anropa dess NewProductRow() metod (vilket vi gör i AddProduct). Den här bristen visar sig när vi vänder oss till att infoga och uppdatera produkter med hjälp av ObjectDataSource. I korthet försöker ObjectDataSource skapa en instans av indataparametrarna. Om BLL-metoden förväntar sig en ProductsRow instans försöker ObjectDataSource skapa en, men misslyckas på grund av bristen på en standardparameterlös konstruktor. Mer information om det här problemet finns i följande två ASP.NET Forums-inlägg: Uppdatera ObjectDataSources med Strongly-Typed DataSets och Problem med ObjectDataSource och Strongly-Typed DataSet.

I både AddProduct och UpdateProductskapar koden sedan en ProductsRow instans och fyller den med de värden som just skickats in. När du tilldelar värden till DataColumns för en DataRow kan olika valideringskontroller på fältnivå utföras. Genom att manuellt lägga tillbaka de skickade värdena i en DataRow kan du därför säkerställa giltigheten för de data som skickas till BLL-metoden. Tyvärr använder inte de starkt typerade DataRow-klasserna som genereras av Visual Studio nullbara typer. I stället måste vi använda NULL metoden för att ange att en viss DataColumn i en DataRow ska motsvara ett SetColumnNameNull() databasvärde.

I UpdateProduct läser vi först in produkten för att uppdatera produkten med GetProductByProductID(productID). Även om detta kan verka som en onödig resa till databasen, kommer den här extra resan att visa sig vara värdefull i framtida självstudier som utforskar optimistisk samtidighet. Optimistisk samtidighet är en teknik som säkerställer att två användare som samtidigt arbetar med samma data inte oavsiktligt skriver över varandras ändringar. Genom att hämta hela posten blir det också enklare att skapa uppdateringsmetoder i BLL som bara ändrar en delmängd av DataRow-objektets kolumner. När vi utforskar SuppliersBLL klassen ser vi ett sådant exempel.

Observera slutligen att ProductsBLL klassen har attributet DataObject tillämpat på den (syntaxen [System.ComponentModel.DataObject] precis före klassinstrukturen längst upp i filen) och att metoderna har Attribut för DataObjectMethodAttribute. Attributet DataObject markerar klassen som ett objekt som lämpar sig för bindning till en ObjectDataSource-kontroll, medan DataObjectMethodAttribute indikerar syftet med metoden. Som vi ser i framtida självstudier gör ASP.NET 2.0:s ObjectDataSource det enkelt att deklarativt komma åt data från en klass. För att filtrera listan över möjliga klasser som ska bindas till i Guiden ObjectDataSource är det som standard endast de klasser som är markerade som DataObjects visas i guidens listruta. Klassen ProductsBLL fungerar lika bra utan dessa attribut, men om du lägger till dem blir det enklare att arbeta med i ObjectDataSource-guiden.

Lägga till de andra klasserna

När klassen ProductsBLL är klar, behöver vi fortfarande lägga till klasser för att arbeta med kategorier, leverantörer och anställda. Ta en stund att skapa följande klasser och metoder med hjälp av begreppen i exemplet ovan:

  • CategoriesBLL.cs

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

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

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

Den enda metod som är värd att notera är SuppliersBLL klassens UpdateSupplierAddress metod. Den här metoden tillhandahåller ett gränssnitt för att uppdatera bara leverantörens adressinformation. Internt läser den här metoden in SupplierDataRow-objektet för det angivna supplierID (med hjälp av GetSupplierBySupplierID), ställer in dess adressrelaterade egenskaper och anropar sedan SupplierDataTables Update-metod. Metoden UpdateSupplierAddress följer:

<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

Se nedladdningen av den här artikeln för min fullständiga implementering av BLL-klasserna.

Steg 2: Åtkomst till typerade datauppsättningar via BLL-klasserna

I den första självstudien såg vi exempel på att arbeta direkt med Typed DataSet programmatiskt, men med tillägget av våra BLL-klasser bör presentationsnivån fungera mot BLL i stället. I exemplet AllProducts.aspx från den första självstudien ProductsTableAdapter användes för att binda listan över produkter till en GridView, som du ser i följande kod:

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

Om du vill använda de nya BLL-klasserna behöver du bara ändra den första kodraden ProductsTableAdapter genom att helt enkelt ersätta objektet med ett ProductBLL objekt:

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

BLL-klasserna kan också nås deklarativt (liksom den typade datauppsättningen) med hjälp av ObjectDataSource. Vi kommer att diskutera ObjectDataSource mer detaljerat i följande genomgångar.

Listan över produkter visas i en GridView

Bild 3: Listan över produkter visas i en GridView (Klicka om du vill visa en bild i full storlek)

Steg 3: Lägga till Field-Level validering i DataRow-klasserna

Validering på fältnivå är kontroller som gäller egenskapsvärdena för affärsobjekten vid infogning eller uppdatering. Några verifieringsregler på fältnivå för produkter är:

  • Fältet ProductName måste vara högst 40 tecken långt
  • Fältet QuantityPerUnit måste vara högst 20 tecken långt
  • Fälten ProductID, ProductNameoch Discontinued krävs, men alla andra fält är valfria
  • Fälten UnitPrice, UnitsInStock, UnitsOnOrderoch ReorderLevel måste vara större än eller lika med noll

Dessa regler kan och bör uttryckas på databasnivå. Teckengränsen för fälten ProductName och QuantityPerUnit samlas in av datatyperna för dessa kolumner i Products tabellen (nvarchar(40)nvarchar(20)respektive ). Huruvida fält är obligatoriska eller valfria uttrycks genom om databastabellkolumnen tillåter NULL s. Det finns fyra kontrollbegränsningar som säkerställer att endast värden som är större än eller lika med noll kan göra det till kolumnerna UnitPrice, UnitsInStock, UnitsOnOrdereller ReorderLevel .

Förutom att framtvinga dessa regler i databasen bör de också tillämpas på datauppsättningsnivå. Faktum är att fältlängden och huruvida ett värde krävs eller är valfritt redan har samlats in för varje DataTable-uppsättning DataColumns. Om du vill se den befintliga valideringen på fältnivå automatiskt går du till DataSet Designer, väljer ett fält från en av DataTables och går sedan till fönstret Egenskaper. Som bild 4 visar har QuantityPerUnit DataColumn i ProductsDataTable en maximal längd på 20 tecken och tillåter NULL värden. Om vi försöker sätta värdet på ProductsDataRow:s QuantityPerUnit-egenskap till en sträng som är längre än 20 tecken kommer en ArgumentException att kastas.

DataColumn tillhandahåller grundläggande Field-Level validering

Bild 4: DataColumn tillhandahåller grundläggande Field-Level validering (klicka om du vill visa en bild i full storlek)

Tyvärr kan vi inte ange gränskontroller, till exempel UnitPrice att värdet måste vara större än eller lika med noll, via fönstret Egenskaper. För att kunna tillhandahålla den här typen av validering på fältnivå måste vi skapa en händelsehanterare för DataTables ColumnChanging-händelse . Som nämnts i föregående självstudie kan datauppsättnings-, DataTables- och DataRow-objekt som skapats av Typed DataSet utökas med hjälp av partiella klasser. Med den här tekniken kan vi skapa en ColumnChanging händelsehanterare för ProductsDataTable klassen. Börja med att skapa en klass i App_Code mappen med namnet ProductsDataTable.ColumnChanging.vb.

Lägg till en ny klass i mappen App_Code

Bild 5: Lägg till en ny klass i mappen App_Code (Klicka om du vill visa en bild i full storlek)

Skapa sedan en händelsehanterare för ColumnChanging händelsen som säkerställer att UnitPricekolumnvärdena , UnitsInStock, UnitsOnOrderoch ReorderLevel (om inte NULL) är större än eller lika med noll. Om en sådan kolumn är utanför tillåtet intervall ska du kasta en 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

Steg 4: Lägga till anpassade affärsregler i BLL-klasserna

Förutom validering på fältnivå kan det finnas anpassade affärsregler på hög nivå som omfattar olika entiteter eller begrepp som inte kan uttryckas på en kolumnnivå, till exempel:

  • Om en produkt upphör kan den UnitPrice inte uppdateras
  • En anställds bosättningsland måste vara detsamma som chefens bosättningsland
  • En produkt kan inte avbrytas om det är den enda produkt som tillhandahålls av leverantören

BLL-klasserna bör innehålla kontroller för att säkerställa efterlevnad av programmets affärsregler. Dessa kontroller kan läggas till direkt i de metoder som de gäller för.

Anta att våra affärsregler föreskriver att en produkt inte kan markeras som avbruten om den var den enda produkten från en viss leverantör. Om produkt X var den enda produkt vi köpte från leverantören Y kunde vi alltså inte markera X som avbruten. Men om leverantören Y försåg oss med tre produkter, A, B och C, så kunde vi markera alla dessa som utgångna. En udda affärsregel, men affärsregler och sunt förnuft är inte alltid överensstämmande!

För att framtvinga den här affärsregeln i metoden UpdateProducts skulle vi börja med att kontrollera om Discontinued har angetts till True och i så fall anropar GetProductsBySupplierID vi för att fastställa hur många produkter vi har köpt från den här produktens leverantör. Om endast en produkt köps från den här leverantören genererar vi en 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

Svara på valideringsfel på presentationsnivån

När vi anropar BLL från presentationsnivån kan vi bestämma om vi ska försöka hantera eventuella undantag som kan uppstå eller låta dem bubbla upp till ASP.NET (vilket genererar HttpApplications Error-händelse). För att hantera ett undantag när vi arbetar med BLL programmatiskt kan vi använda en Try... Catch block, som visas i följande exempel:

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

Som vi kommer att se i framtida handledningar kan hantering av undantag som bubblar upp från BLL när du använder en datakontroll på webben för att infoga, uppdatera eller ta bort data hanteras direkt i en händelsehanterare, snarare än att behöva omsluta kod i Try...Catch-block.

Sammanfattning

Ett väl utformat program är utformat i distinkta skikt, som var och en kapslar in en viss roll. I den första självstudien i den här artikelserien skapade vi ett dataåtkomstlager med hjälp av typade datauppsättningar. I den här självstudien skapade vi ett affärslogiklager som en serie klasser i programmets App_Code mapp som anropar ned till vår DAL. BLL implementerar logiken på fältnivå och affärsnivå för vårt program. Förutom att skapa en separat BLL, som vi gjorde i den här självstudien, är ett annat alternativ att utöka TableAdapters metoder med hjälp av partiella klasser. Men med den här tekniken kan vi inte åsidosätta befintliga metoder och separerar inte heller vår DAL och vår BLL lika rent som den metod vi har använt i den här artikeln.

När DAL och BLL är klara är vi redo att börja på presentationsskiktet. I nästa självstudie tar vi en kort omväg från dataåtkomstämnen och definierar en konsekvent sidlayout för användning i självstudierna.

Lycka till med programmerandet!

Om författaren

Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.

Särskilt tack till

Den här självstudieserien granskades av många användbara granskare. Huvudgranskare för den här självstudien var Liz Shulok, Dennis Patterson, Carlos Santos och Hilton Giesenow. Vill du granska mina kommande MSDN-artiklar? Om så är fallet, hör av dig på mitchell@4GuysFromRolla.com.