Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Scott Mitchell
In diesem Lernprogramm erfahren Sie, wie Sie Ihre Geschäftsregeln in eine Business Logic Layer (BLL) zentralisieren, die als Vermittler für den Datenaustausch zwischen der Präsentationsebene und dem DAL dient.
Einleitung
Die im ersten Lernprogramm erstellte Datenzugriffsebene (Data Access Layer, DAL) trennt die Datenzugriffslogik sauber von der Präsentationslogik. Während die DAL jedoch die Datenzugriffsdetails sauber von der Präsentationsebene trennt, erzwingt sie keine Geschäftsregeln, die gelten können. Für unsere Anwendung möchten wir z. B. die Änderungen der CategoryID
Felder oder SupplierID
Felder der Products
Tabelle möglicherweise nicht zulassen, wenn das Discontinued
Feld auf 1 festgelegt ist, oder wir möchten möglicherweise Dienstalterregelungen durchsetzen, um Situationen zu verhindern, in denen ein Mitarbeiter von jemandem betreut wird, der später eingestellt wurde. Ein weiteres gängiges Szenario ist die Autorisierung, vielleicht können nur Benutzer in einer bestimmten Rolle Produkte löschen oder den UnitPrice
Wert ändern.
In diesem Lernprogramm erfahren Sie, wie Sie diese Geschäftsregeln in eine Business Logic Layer (BLL) zentralisieren, die als Vermittler für den Datenaustausch zwischen der Präsentationsschicht und dem DAL dient. In einer realen Anwendung sollte die BLL als separates Klassenbibliotheksprojekt implementiert werden; Für diese Lernprogramme implementieren wir die BLL jedoch als Eine Reihe von Klassen in unserem App_Code
Ordner, um die Projektstruktur zu vereinfachen. Abbildung 1 veranschaulicht die Architekturbeziehungen zwischen der Präsentationsebene, BLL und DAL.
Abbildung 1: Die BLL trennt die Präsentationsschicht von der Datenzugriffsschicht und legt Geschäftsregeln fest.
Schritt 1: Erstellen der BLL-Klassen
Unsere BLL besteht aus vier Klassen, einer für jeden TableAdapter im DAL; Jede dieser BLL-Klassen verfügt über Methoden zum Abrufen, Einfügen, Aktualisieren und Löschen aus dem jeweiligen TableAdapter im DAL, wobei die entsprechenden Geschäftsregeln angewendet werden.
Um die DAL- und BLL-bezogenen Klassen sauberer zu trennen, erstellen wir zwei Unterordner im App_Code
Ordner und DAL
BLL
. Klicken Sie einfach mit der rechten Maustaste auf den App_Code
Ordner im Projektmappen-Explorer, und wählen Sie "Neuer Ordner" aus. Verschieben Sie nach dem Erstellen dieser beiden Ordner das typierte DataSet, das im ersten Lernprogramm erstellt wurde, in den DAL
Unterordner.
Erstellen Sie als Nächstes die vier BLL-Klassendateien im BLL
Unterordner. Klicken Sie dazu mit der rechten Maustaste auf den BLL
Unterordner, wählen Sie "Neues Element hinzufügen" aus, und wählen Sie dann die Vorlage "Klasse" aus. Benennen Sie die vier Klassen ProductsBLL
, CategoriesBLL
, SuppliersBLL
und EmployeesBLL
.
Abbildung 2: Hinzufügen von vier neuen Klassen zum App_Code
Ordner
Als Nächstes fügen wir den einzelnen Klassen Methoden hinzu, um die Methoden, die für die TableAdapters im ersten Tutorial definiert sind, einfach zu umwickeln. Diese Methoden werden vorerst nur direkt in die DAL aufrufen; wir kehren später zurück, um die erforderliche Geschäftslogik hinzuzufügen.
Hinweis
Wenn Sie Visual Studio Standard Edition oder höher verwenden (d. h., Sie verwenden Visual Web Developer nicht), können Sie ihre Klassen optional visuell mit dem Klassen-Designer entwerfen. Weitere Informationen zu diesem neuen Feature in Visual Studio finden Sie im Class Designer-Blog .
Für die ProductsBLL
Klasse müssen wir insgesamt sieben Methoden hinzufügen:
-
GetProducts()
gibt alle Produkte zurück. -
GetProductByProductID(productID)
gibt das Produkt mit der angegebenen Produkt-ID zurück. -
GetProductsByCategoryID(categoryID)
gibt alle Produkte aus der angegebenen Kategorie zurück. -
GetProductsBySupplier(supplierID)
gibt alle Produkte des angegebenen Lieferanten zurück. -
AddProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued)
fügt ein neues Produkt mithilfe der übergebenen Werte in die Datenbank ein; gibt denProductID
Wert des neu eingefügten Datensatzes zurück. -
UpdateProduct(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued, productID)
aktualisiert ein vorhandenes Produkt in der Datenbank mithilfe der übergebenen Werte; gibt zurücktrue
, wenn genau eine Zeile aktualisiert wurde,false
andernfalls -
DeleteProduct(productID)
löscht das angegebene Produkt aus der Datenbank.
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;
}
}
Die Methoden, die einfach Daten GetProducts
, GetProductByProductID
, GetProductsByCategoryID
und GetProductBySuppliersID
zurückgeben, sind ziemlich einfach, da sie lediglich Methoden des DAL aufrufen. In einigen Szenarien gibt es möglicherweise Geschäftsregeln, die auf dieser Ebene implementiert werden müssen (z. B. Autorisierungsregeln basierend auf dem aktuell angemeldeten Benutzer oder der Rolle, zu der der Benutzer gehört), wir lassen diese Methoden einfach as-is. Für diese Methoden dient die BLL dann lediglich als Proxy, über den die Präsentationsschicht auf die zugrunde liegenden Daten von der Datenzugriffsebene zugreift.
Die AddProduct
Methoden UpdateProduct
übernehmen beide als Parameter die Werte für die verschiedenen Produktfelder und fügen ein neues Produkt hinzu oder aktualisieren ein vorhandenes Produkt. Da viele Spalten der Product
-Tabelle Werte wie NULL
, CategoryID
, SupplierID
und UnitPrice
akzeptieren können, verwenden die Eingabeparameter für AddProduct
und UpdateProduct
, die solchen Spalten zugeordnet sind, nullable Typen. Nullable-Typen sind neu in .NET 2.0 und stellen eine Technik bereit, die angibt, ob ein Werttyp stattdessen sein null
soll. In C# können Sie einen Werttyp als Typ kennzeichnen, der NULL-Werte zulässt, indem Sie nach dem Typ (wie int? x;
) hinzufügen?
. Weitere Informationen finden Sie im Abschnitt NULLABLE-Typen im C#-Programmierhandbuch .
Alle drei Methoden geben einen booleschen Wert zurück, der angibt, ob eine Zeile eingefügt, aktualisiert oder gelöscht wurde, da der Vorgang möglicherweise nicht zu einer betroffenen Zeile führt. Wenn der Seitenentwickler z. B. DeleteProduct
aufruft und dabei ein ProductID
für ein nicht vorhandenes Produkt übergibt, hat die an die Datenbank ausgegebene DELETE
-Anweisung keine Auswirkungen und daher gibt die DeleteProduct
Methode false
zurück.
Beachten Sie, dass wir beim Hinzufügen eines neuen Produkts oder beim Aktualisieren eines vorhandenen Produkts die Feldwerte des neuen oder geänderten Produkts als Liste von Skalaren anstelle einer ProductsRow
Instanz erfassen. Dieser Ansatz wurde ausgewählt, da die ProductsRow
Klasse von der ADO.NET DataRow
Klasse abgeleitet ist, die keinen standardparameterlosen Konstruktor aufweist. Um eine neue ProductsRow
Instanz zu erstellen, müssen wir zuerst eine ProductsDataTable
Instanz erstellen und dann ihre NewProductRow()
Methode aufrufen, was wir in AddProduct
tun. Dieses Manko tritt in Erscheinung, wenn wir Produkte mit der ObjectDataSource einfügen und aktualisieren. Kurz gesagt versucht objectDataSource, eine Instanz der Eingabeparameter zu erstellen. Wenn die BLL-Methode eine ProductsRow
Instanz erwartet, versucht Die ObjectDataSource, eine instanz zu erstellen, schlägt jedoch aufgrund des Fehlens eines standardparameterlosen Konstruktors fehl. Weitere Informationen zu diesem Problem finden Sie in den folgenden beiden ASP.NET Forenbeiträgen: Aktualisieren von ObjectDataSources mit Strongly-Typed DataSets und Problem mit ObjectDataSource und Strongly-Typed DataSet.
Als Nächstes wird sowohl in AddProduct
als auch in UpdateProduct
eine ProductsRow
-Instanz vom Code erstellt und mit den soeben übergebenen Werten gefüllt. Beim Zuweisen von Werten zu DataColumns eines DataRow können verschiedene Validierungsprüfungen auf Feldebene auftreten. Daher trägt das manuelle Einfügen der übergebenen Werte in ein DataRow-Objekt dazu bei, die Gültigkeit der an die BLL-Methode übergebenen Daten sicherzustellen. Leider verwenden die stark typisierten DataRow-Klassen, die von Visual Studio generiert werden, keine Nullable-Typen. Um anzugeben, dass eine bestimmte Datenspalte in einer Datenzeile einem NULL
Datenbankwert entsprechen sollte, müssen wir die SetColumnNameNull()
Methode verwenden.
In UpdateProduct
laden wir zuerst das Produkt, um es mit GetProductByProductID(productID)
zu aktualisieren. Auch wenn dies wie eine unnötige Datenbankanfrage scheint, wird sich diese zusätzliche Anfrage in zukünftigen Tutorials als lohnend erweisen, die optimistische Parallelität erkunden. Optimistische Parallelität ist eine Technik, um sicherzustellen, dass zwei Benutzer, die gleichzeitig an denselben Daten arbeiten, nicht versehentlich die Änderungen eines anderen überschreiben. Das Abrufen des gesamten Datensatzes erleichtert auch das Erstellen von Aktualisierungsmethoden in der BLL, die nur eine Teilmenge der Spalten von DataRow ändern. Wenn wir die SuppliersBLL
Klasse untersuchen, werden wir ein solches Beispiel sehen.
Beachten Sie schließlich, dass die ProductsBLL
Klasse das DataObject-Attribut zugewiesen hat (die [System.ComponentModel.DataObject]
Syntax direkt vor der Klassenanweisung am oberen Rand der Datei), und die Methoden weisen DataObjectMethodAttribute-Attribute auf. Das DataObject
Attribut kennzeichnet die Klasse als objekt, das für die Bindung an ein ObjectDataSource-Steuerelement geeignet ist, während der DataObjectMethodAttribute
Zweck der Methode angegeben wird. Wie wir in zukünftigen Lernprogrammen sehen werden, erleichtert ASP.NET 2.0 ObjectDataSource den deklarativen Zugriff auf Daten aus einer Klasse. Um die Liste der möglichen Klassen, die im Assistenten von ObjectDataSource gebunden werden sollen, zu filtern, werden standardmäßig nur die Klassen angezeigt, die als DataObjects
markiert sind und in der Dropdownliste des Assistenten erscheinen. Die ProductsBLL
Klasse funktioniert genauso gut ohne diese Attribute, aber das Hinzufügen erleichtert das Arbeiten mit dem Assistenten von ObjectDataSource.
Hinzufügen der anderen Klassen
Nach Abschluss der ProductsBLL
Klasse müssen wir weiterhin die Klassen für die Arbeit mit Kategorien, Lieferanten und Mitarbeitern hinzufügen. Nehmen Sie sich einen Moment Zeit, um die folgenden Klassen und Methoden mithilfe der Konzepte aus dem obigen Beispiel zu erstellen:
CategoriesBLL.cs
GetCategories()
GetCategoryByCategoryID(categoryID)
SuppliersBLL.cs
GetSuppliers()
GetSupplierBySupplierID(supplierID)
GetSuppliersByCountry(country)
UpdateSupplierAddress(supplierID, address, city, country)
EmployeesBLL.cs
GetEmployees()
GetEmployeeByEmployeeID(employeeID)
GetEmployeesByManager(managerID)
Die eine Methode, die zu notieren ist, ist die Methode der SuppliersBLL
Klasse UpdateSupplierAddress
. Diese Methode stellt eine Schnittstelle zum Aktualisieren nur der Adressinformationen des Lieferanten bereit. Intern liest diese Methode das SupplierDataRow
-Objekt für die angegebene supplierID
(unter Verwendung von GetSupplierBySupplierID
) ein, legt die adressbezogenen Eigenschaften fest und ruft dann die Methode SupplierDataTable
von Update
auf. Die UpdateSupplierAddress
Methode folgt:
[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;
}
}
Bitte konsultieren Sie den Download dieses Artikels für meine vollständige Implementierung der BLL-Klassen.
Schritt 2: Zugreifen auf die typierten DataSets über die BLL-Klassen
Im ersten Tutorial haben wir Beispiele für die programmatische Arbeit mit dem Typed DataSet gesehen, aber mit der Einführung unserer BLL-Klassen sollte die Präsentationsschicht stattdessen mit der BLL interagieren.
AllProducts.aspx
Im Beispiel aus dem ersten Lernprogramm wurde die ProductsTableAdapter
Liste der Produkte an ein GridView gebunden, wie im folgenden Code gezeigt:
ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
Um die neuen BLL-Klassen zu verwenden, müssen Sie lediglich die erste Codezeile ändern, indem Sie das ProductsTableAdapter
Objekt durch ein ProductBLL
Objekt ersetzen.
ProductsBLL productLogic = new ProductsBLL();
GridView1.DataSource = productLogic.GetProducts();
GridView1.DataBind();
Auf die BLL-Klassen kann, ebenso wie auf das Typed DataSet, deklarativ zugegriffen werden, indem die ObjectDataSource verwendet wird. In den folgenden Lernprogrammen werden wir die ObjectDataSource ausführlicher besprechen.
Abbildung 3: Die Liste der Produkte wird in einer GridView angezeigt (Zum Anzeigen des Bilds mit voller Größe klicken)
Schritt 3: Hinzufügen der Field-Level-Validierung für die DataRow-Klassen
Bei der Überprüfung auf Feldebene handelt es sich um Überprüfungen, die sich auf die Eigenschaftswerte der Geschäftsobjekte beziehen, wenn sie eingefügt oder aktualisiert werden. Einige Gültigkeitsprüfungsregeln auf Feldebene für Produkte umfassen:
- Das
ProductName
Feld muss maximal 40 Zeichen lang sein. - Das
QuantityPerUnit
Feld muss maximal 20 Zeichen lang sein. - Die
ProductID
FelderProductName
undDiscontinued
Felder sind erforderlich, aber alle anderen Felder sind optional. - Die Felder
UnitPrice
,UnitsInStock
,UnitsOnOrder
undReorderLevel
müssen größer oder gleich Null sein.
Diese Regeln können und sollten auf Datenbankebene ausgedrückt werden. Die Zeichenbeschränkung in den Feldern ProductName
und QuantityPerUnit
wird durch die Datentypen dieser Spalten in der Products
-Tabelle erfasst (nvarchar(40)
bzw. nvarchar(20)
). Gibt an, ob Felder erforderlich und optional angegeben werden, wenn die Datenbanktabellenspalte s zulässt NULL
. Es existieren vier Prüfeinschränkungen, die sicherstellen, dass nur Werte größer als oder gleich Null in die UnitPrice
, UnitsInStock
, UnitsOnOrder
oder ReorderLevel
Spalten gelangen können.
Zusätzlich zum Erzwingen dieser Regeln in der Datenbank sollten sie auch auf DataSet-Ebene erzwungen werden. Tatsächlich werden die Feldlänge und ob ein Wert erforderlich oder optional ist, bereits für jeden DataTable-Satz von DataColumns erfasst. Um die vorhandene Überprüfung auf Feldebene automatisch anzuzeigen, wechseln Sie zum DataSet-Designer, wählen Sie ein Feld aus einem der DataTables aus, und wechseln Sie dann zum Eigenschaftenfenster. Wie in Abbildung 4 gezeigt, hat der QuantityPerUnit
DataColumn im ProductsDataTable
eine maximale Länge von 20 Zeichen und erlaubt NULL
Werte. Wenn wir versuchen, die Eigenschaft von ProductsDataRow
auf einen Zeichenfolgenwert festzulegen, der länger als 20 Zeichen ist, wird ein QuantityPerUnit
ausgelöst.
Abbildung 4: Das DataColumn stellt eine einfache Field-Level Überprüfung bereit (Klicken Sie, um das Bild in voller Größe anzuzeigen)
Leider können wir keine Begrenzungsprüfungen angeben, wie zum Beispiel, dass der UnitPrice
-Wert größer oder gleich Null sein muss, über das Eigenschaftenfenster. Um diese Art von Überprüfung auf Feldebene bereitzustellen, müssen wir einen Ereignishandler für das ColumnChanging-Ereignis der DataTable erstellen. Wie im vorherigen Lernprogramm erwähnt, können die dataSet-, DataTables- und DataRow-Objekte, die vom Typed DataSet erstellt wurden, über die Verwendung partieller Klassen erweitert werden. Mit dieser Technik können wir einen ColumnChanging
Ereignishandler für die ProductsDataTable
Klasse erstellen. Erstellen Sie zunächst eine Klasse im Ordner mit dem App_Code
Namen ProductsDataTable.ColumnChanging.cs
.
Abbildung 5: Hinzufügen einer neuen Klasse zum App_Code
Ordner (Klicken Sie hier, um das Bild in voller Größe anzuzeigen)
Erstellen Sie als Nächstes einen Ereignishandler für das ColumnChanging
-Ereignis, der sicherstellt, dass die Werte der UnitPrice
, UnitsInStock
, UnitsOnOrder
und ReorderLevel
-Spalten (wenn nicht NULL
) größer oder gleich null sind. Wenn eine solche Spalte außerhalb des zulässigen Bereichs liegt, werfen Sie ein 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);
}
}
}
}
}
Schritt 4: Hinzufügen von benutzerdefinierten Geschäftsregeln zu den BLL-Klassen
Zusätzlich zur Überprüfung auf Feldebene können benutzerdefinierte Geschäftsregeln auf hoher Ebene vorhanden sein, die verschiedene Entitäten oder Konzepte umfassen, die nicht auf der Ebene einer einzelnen Spalte ausgedrückt werden können, z. B.:
- Wenn ein Produkt eingestellt wird, kann sein
UnitPrice
nicht aktualisiert werden. - Das Wohnsitzland eines Arbeitnehmers muss mit dem Wohnsitzland seines Vorgesetzten übereinstimmen.
- Ein Produkt kann nicht eingestellt werden, wenn es sich um das einzige Produkt handelt, das vom Lieferanten bereitgestellt wird.
Die BLL-Klassen sollten Prüfungen enthalten, um die Einhaltung der Geschäftsregeln der Anwendung sicherzustellen. Diese Prüfungen können direkt zu den Methoden hinzugefügt werden, auf die sie angewendet werden.
Stellen Sie sich vor, unsere Geschäftsregeln würden vorschreiben, dass ein Produkt nicht als ausgelaufen markiert werden darf, wenn es das einzige Produkt eines bestimmten Lieferanten ist. Wenn das Produkt X das einzige Produkt war, das wir von Lieferanten Y erworben haben, konnten wir X nicht als nicht mehr eingestellt kennzeichnen; Wenn der Lieferant Y uns jedoch drei Produkte, A, B und C, geliefert hat, könnten wir alle diese als nicht mehr eingestellt kennzeichnen. Eine merkwürdige Geschäftsregel, aber Geschäftsregeln und gesunder Menschenverstand stimmen nicht immer überein!
Um diese Geschäftsregel in der UpdateProducts
Methode zu erzwingen, überprüfen wir zunächst, ob Discontinued
auf true
gesetzt wurde, und falls dies der Fall ist, würden wir GetProductsBySupplierID
aufrufen, um festzustellen, wie viele Produkte wir vom Lieferanten dieses Produkts erworben haben. Wenn nur ein Produkt von diesem Lieferanten gekauft wird, werfen wir einen 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;
}
Reagieren auf Validierungsfehler in der Darstellungsschicht
Beim Aufrufen der BLL aus der Präsentationsschicht können wir entscheiden, ob wir versuchen sollen, Ausnahmen zu behandeln, die ausgelöst werden können, oder sie an ASP.NET weiterreichen (wodurch das HttpApplication
Error
-Ereignis ausgelöst wird). Um eine Ausnahme bei der programmgesteuerten Arbeit mit der BLL zu behandeln, können wir einen try... catch-Block , wie das folgende Beispiel zeigt:
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);
}
Wie wir in zukünftigen Tutorials sehen werden, können Ausnahmen, die sich von der BLL propagieren, direkt in einem Ereignishandler behandelt werden, wenn ein Daten-Websteuerelement zum Einfügen, Aktualisieren oder Löschen von Daten verwendet wird, anstatt den Code in try...catch
-Blöcke einwickeln zu müssen.
Zusammenfassung
Eine gut gestaltete Anwendung wird in unterschiedlichen Ebenen erstellt, von denen jede eine bestimmte Rolle kapselt. Im ersten Lernprogramm dieser Artikelreihe haben wir eine Datenzugriffsebene mit typierten DataSets erstellt. In diesem Lernprogramm haben wir eine Business Logic Layer als eine Reihe von Klassen im Ordner unserer Anwendung App_Code
erstellt, die in unserem DAL aufrufen. Die BLL implementiert die Logik auf Feld- und Geschäftsebene für unsere Anwendung. Neben dem Erstellen einer separaten BLL, wie wir es in diesem Lernprogramm getan haben, besteht eine weitere Option darin, die Methoden der TableAdapters mit Hilfe von partiellen Klassen zu erweitern. Die Verwendung dieser Technik erlaubt es uns jedoch nicht, vorhandene Methoden zu überschreiben, noch trennt sie unsere DAL und unsere BLL so sauber wie der in diesem Artikel beschriebene Ansatz.
Mit dal und BLL sind wir bereit, auf unserer Präsentationsebene zu beginnen. Im nächsten Lernprogramm nehmen wir einen kurzen Umweg von Datenzugriffsthemen und definieren ein einheitliches Seitenlayout für die Verwendung in den Lernprogrammen.
Glückliche Programmierung!
Zum Autor
Scott Mitchell, Autor von sieben ASP/ASP.NET Büchern und Gründer von 4GuysFromRolla.com, arbeitet seit 1998 mit Microsoft Web Technologies zusammen. Scott arbeitet als unabhängiger Berater, Trainer und Schriftsteller. Sein neuestes Buch ist Sams Teach Yourself ASP.NET 2.0 in 24 Stunden. Er kann bei mitchell@4GuysFromRolla.comerreicht werden.
Besonderer Dank an
Diese Lernprogrammreihe wurde von vielen hilfreichen Prüfern überprüft. Leitende Prüfer für dieses Lernprogramm waren Liz Shulok, Dennis Patterson, Carlos Santos und Hilton Giesenow. Möchten Sie meine bevorstehenden MSDN-Artikel überprüfen? Wenn ja, schicken Sie mir eine Nachricht an mitchell@4GuysFromRolla.com.