PetShop是微軟公司提供的一個巨大而復雜的玩具。MSDN上說,這個架構可以擴展成N層架構。雖然覺得這話有自诩的成分在其中,但是在看完整個代碼後,不得不承認,這個PetShop還真是個復雜的“玩具”。
那就玩玩好了,試著自己創建了一個。這裡記錄下:
按照習慣,還是從DAL層開始創建。依照以下順序創建項目:
1、DBUtility
這個是數據庫訪問的基礎,包含對應SqlServer的訪問類SqlHelper和對應Oracle的訪問類OracleHelper。創建好後,直接把PetShop中的兩個類Ctrl+C過來就好。嘿嘿,我是很懶的。要注意的是這裡使用了ConfigurationManager類來讀取配置文件中的連接字符串
//數據庫連接字符串
public static readonly string ConnectionStringLocalTransaction = ConfigurationManager.ConnectionStrings["SQLConnString1"].ConnectionString;
public static readonly string ConnectionStringInventoryDistributedTransaction = ConfigurationManager.ConnectionStrings["SQLConnString2"].ConnectionString;
public static readonly string ConnectionStringOrderDistributedTransaction = ConfigurationManager.ConnectionStrings["SQLConnString3"].ConnectionString;
public static readonly string ConnectionStringProfile = ConfigurationManager.ConnectionStrings["SQLProfileConnString"].ConnectionString;
要在配置文件中提供相應的配置信息才能夠運行。順便說下,PetShop中的數據庫分為4個:
MSPetShop4——PetShop信息庫,存儲商品信息,供應商信息等基礎信息
MSPetShop4Orders——訂單庫,存儲訂單信息
MSPetShop4Profiles——用戶信息庫,存儲用戶信息、購物車信息等
MSPetShop4Services——服務信息庫,存儲各種應用程序信息,dll路徑等。
上面的四個連接,就分別對應這四個數據庫
2、Model
這個是系統的實體類,按照自己的業務實體創建實體類吧。一般來說,我們的實體類有可能會用到Soap這樣的協議中去,所以支持序列化還是必要的。不復雜,直接在類的頭上加上[Serializable]這個特性描述就好。如:
/// <summary>
/// 訂單業務實體
/// </summary>
[Serializable]
public class OrderInfo {
// 成員變量
private int orderId;
private DateTime date;
private string userId;
private CreditCardInfo creditCard;
private AddressInfo billingAddress;
private AddressInfo shippingAddress;
private decimal orderTotal;
private LineItemInfo[] lineItems;
private Nullable<int> authorizationNumber;
/// <summary>
/// 默認構造方法
/// WebService 的序列化機制需要
/// </summary>
public OrderInfo() { }
/// <summary>
/// 帶初始值的構造方法
/// </summary>
/// <param name="orderId">主鍵</param>
/// <param name="date">訂貨日期</param>
/// <param name="userId">訂貨人(編號)</param>
/// <param name="creditCard">訂貨信用卡</param>
/// <param name="billing">訂貨地址</param>
/// <param name="shipping">送貨地址</param>
/// <param name="total">訂單價值</param>
/// <param name="line">訂單商品(數組)</param>
/// <param name="authorization">信用卡注冊號</param>
public OrderInfo(int orderId, DateTime date, string userId, CreditCardInfo creditCard, AddressInfo billing, AddressInfo shipping, decimal total, LineItemInfo[] line, Nullable<int> authorization) {
this.orderId = orderId;
this.date = date;
this.userId = userId;
this.creditCard = creditCard;
this.billingAddress = billing;
this.shippingAddress = shipping;
this.orderTotal = total;
this.lineItems = line;
this.authorizationNumber = authorization;
}
// 屬性
public int OrderId {
get { return orderId; }
set { orderId = value; }
}
public DateTime Date {
get { return date; }
set { date = value; }
}
public string UserId {
get { return userId; }
set { userId = value; }
}
public CreditCardInfo CreditCard {
get { return creditCard; }
set { creditCard = value; }
}
public AddressInfo BillingAddress {
get { return billingAddress; }
set { billingAddress = value; }
}
public AddressInfo ShippingAddress {
get { return shippingAddress; }
set { shippingAddress = value; }
}
public decimal OrderTotal {
get { return orderTotal; }
set { orderTotal = value; }
}
public LineItemInfo[] LineItems {
get { return lineItems; }
set { lineItems = value; }
}
public Nullable<int> AuthorizationNumber {
get {return authorizationNumber;}
set {authorizationNumber = value;}
}
}
要注意的是需要一個默認構造方法,即使你沒有使用它。因為在WebService中如果序列化這個實體,需要這個默認構造
3、IDAL
從這裡開始我們要開始添加引用了。這裡我們要使用到之前建立的實體類,所以引用Model。
PetShop中使用了抽象工廠模式,所以,現在我們要創建接口了。對應每個Model中的實體創建各自的接口。還是拿Order來說,創建如下的類:
/// <summary>
/// DAL中訂單對象接口
/// </summary>
public interface IOrder {
/// <summary>
/// 插入訂單
/// </summary>
/// <param name="order">訂單業務實體</param>
/// <returns>訂單編號</returns>
void Insert(OrderInfo order);
/// <summary>
/// 按訂單ID, 獲取訂單信息
/// </summary>
/// <param name="orderId">訂單ID</param>
/// <returns>訂單業務實體</returns>
OrderInfo GetOrder(int orderId);
}
一般來說,接口中包含的方法,就是在外部要使用的那些方法了,所以這裡開始就和業務的邏輯有些相關了。要決定如何使用。如在PetShop中針對商品(Item)類就是如下建立接口的:
/// <summary>
/// DAL中商品對象接口
/// </summary>
public interface IItem{
/// <summary>
/// 按產品ID, 獲取商品對象
/// </summary>
/// <param name="productId">產品ID</param>
/// <returns>商品實體類集合接口</returns>
IList<ItemInfo> GetItemsByProduct(string productId);
/// <summary>
/// 按商品ID, 獲取商品信息
/// </summary>
/// <param name="itemId">商品ID</param>
/// <returns>商品業務實體</returns>
ItemInfo GetItem(string itemId);
}
4、SQLServerDAL/OracleDAL
好了,可以針對數據庫創建各自的具體數據庫訪問和命令執行了。要用到之前的所有項目,所以引用吧。這裡需要引用DBUtility、Model、IDAL
針對不同的實體創建類吧,要實現之前針對實體創建的接口才行,還是拿Order來說,如下:
5、DALFactory
public class Order : IOrder ...{
//靜態常量,T-SQL語句,參數名等
private const string SQL_INSERT_ORDER = "Declare @ID int; Declare @ERR int; INSERT INTO Orders VALUES(@UserId, @Date, @ShipAddress1, @ShipAddress2, @ShipCity, @ShipState, @ShipZip, @ShipCountry, @BillAddress1, @BillAddress2, @BillCity, @BillState, @BillZip, @BillCountry, ''UPS'', @Total, @BillFirstName, @BillLastName, @ShipFirstName, @ShipLastName, @AuthorizationNumber, ''US_en''); SELECT @ID=@@IDENTITY; INSERT INTO OrderStatus VALUES(@ID, @ID, GetDate(), ''P''); SELECT @ERR=@@ERROR;";
private const string SQL_INSERT_ITEM = "INSERT INTO LineItem VALUES( ";
private const string SQL_SELECT_ORDER = "SELECT o.OrderDate, o.UserId, o.CardType, o.CreditCard, o.ExprDate, o.BillToFirstName, o.BillToLastName, o.BillAddr1, o.BillAddr2, o.BillCity, o.BillState, BillZip, o.BillCountry, o.ShipToFirstName, o.ShipToLastName, o.ShipAddr1, o.ShipAddr2, o.ShipCity, o.ShipState, o.ShipZip, o.ShipCountry, o.TotalPrice, l.ItemId, l.LineNum, l.Quantity, l.UnitPrice FROM Orders as o, lineitem as l WHERE o.OrderId = @OrderId AND o.orderid = l.orderid";
private const string PARM_USER_ID = "@UserId";
private const string PARM_DATE = "@Date";
private const string PARM_SHIP_ADDRESS1 = "@ShipAddress1";
private const string PARM_SHIP_ADDRESS2 = "@ShipAddress2";
private const string PARM_SHIP_CITY = "@ShipCity";
private const string PARM_SHIP_STATE = "@ShipState";
private const string PARM_SHIP_ZIP = "@ShipZip";
private const string PARM_SHIP_COUNTRY = "@ShipCountry";
private const string PARM_BILL_ADDRESS1 = "@BillAddress1";
private const string PARM_BILL_ADDRESS2 = "@BillAddress2";
private const string PARM_BILL_CITY = "@BillCity";
private const string PARM_BILL_STATE = "@BillState";
private const string PARM_BILL_ZIP = "@BillZip";
private const string PARM_BILL_COUNTRY = "@BillCountry";
private const string PARM_TOTAL = "@Total";
private const string PARM_BILL_FIRST_NAME = "@BillFirstName";
private const string PARM_BILL_LAST_NAME = "@BillLastName";
private const string PARM_SHIP_FIRST_NAME = "@ShipFirstName";
private const string PARM_SHIP_LAST_NAME = "@ShipLastName";
private const string PARM_AUTHORIZATION_NUMBER = "@AuthorizationNumber";
private const string PARM_ORDER_ID = "@OrderId";
private const string PARM_LINE_NUMBER = "@LineNumber";
private const string PARM_ITEM_ID = "@ItemId";
private const string PARM_QUANTITY = "@Quantity";
private const string PARM_PRICE = "@Price";
/**//// <summary>
/// 插入一個訂單
/// </summary>
/// <param name="order">OrderInfo訂單</param>
public void Insert(OrderInfo order) ...{
StringBuilder strSQL = new StringBuilder();
//獲取每個command的parameter數組
SqlParameter[] orderParms = GetOrderParameters();
SqlCommand cmd = new SqlCommand();
// 初始化參數(parameter)
orderParms[0].Value = order.UserId;
orderParms[1].Value = order.Date;
orderParms[2].Value = order.ShippingAddress.Address1;
orderParms[3].Value = order.ShippingAddress.Address2;
orderParms[4].Value = order.ShippingAddress.City;
orderParms[5].Value = order.ShippingAddress.State;
orderParms[6].Value = order.ShippingAddress.Zip;
orderParms[7].Value = order.ShippingAddress.Country;
orderParms[8].Value = order.BillingAddress.Address1;
orderParms[9].Value = order.BillingAddress.Address2;
orderParms[10].Value = order.BillingAddress.City;
orderParms[11].Value = order.BillingAddress.State;
orderParms[12].Value = order.BillingAddress.Zip;
orderParms[13].Value = order.BillingAddress.Country;
orderParms[14].Value = order.OrderTotal;
orderParms[15].Value = order.BillingAddress.FirstName;
orderParms[16].Value = order.BillingAddress.LastName;
orderParms[17].Value = order.ShippingAddress.FirstName;
orderParms[18].Value = order.ShippingAddress.LastName;
orderParms[19].Value = order.AuthorizationNumber.Value;
foreach (SqlParameter parm in orderParms)
cmd.Parameters.Add(parm);
// 創建數據庫連接
using (SqlConnection conn = new SqlConnection(SqlHelper.ConnectionStringOrderDistributedTransaction)) ...{
// 插入訂單狀態
strSQL.Append(SQL_INSERT_ORDER);
SqlParameter[] itemParms;
// 循環所購商品項, 插入訂單
int i = 0;
foreach (LineItemInfo item in order.LineItems) ...{
strSQL.Append(SQL_INSERT_ITEM).Append(" @ID").Append(", @LineNumber").Append(i).Append(", @ItemId").Append(i).Append(", @Quantity").Append(i).Append(", @Price").Append(i).Append("); SELECT @ERR=@ERR+@@ERROR;");
//取得緩存的參數(parameter)
itemParms = GetItemParameters(i);
itemParms[0].Value = item.Line;
itemParms[1].Value = item.ItemId;
itemParms[2].Value = item.Quantity;
itemParms[3].Value = item.Price;
//向command中加入參數
foreach (SqlParameter parm in itemParms)
cmd.Parameters.Add(parm);
i++;
}
conn.Open();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = strSQL.Append("SELECT @ID, @ERR").ToString();
// 讀取查詢返回, 應該是返回錯誤數量
using (SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection)) ...{
// 讀取返回的 @ERR
rdr.Read();
// 如果錯誤數量不為0則throw一個異常
if (rdr.GetInt32(1) != 0)
throw new ApplicationException("DATA INTEGRITY ERROR ON ORDER INSERT - ROLLBACK ISSUED");
}
//清除command中的參數數組
cmd.Parameters.Clear();
}
}
/**//// <summary>
/// 從數據庫中讀取一個訂單
/// </summary>
/// <param name="orderId">訂單編號</param>
/// <returns>所有訂單的信息</returns>
public OrderInfo GetOrder(int orderId) ...{
OrderInfo order = new OrderInfo();
//創建參數
SqlParameter parm = new SqlParameter(PARM_ORDER_ID, SqlDbType.Int);
parm.Value = orderId;
//執行查詢讀取訂單
using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringOrderDistributedTransaction, CommandType.Text, SQL_SELECT_ORDER, parm)) ...{
if (rdr.Read()) ...{
//從第一行中讀出訂單頭
AddressInfo billingAddress = new AddressInfo(rdr.GetString(5), rdr.GetString(6), rdr.GetString(7), rdr.GetString(8), rdr.GetString(9), rdr.GetString(10), rdr.GetString(11), rdr.GetString(12), null, "email");
AddressInfo shippingAddress = new AddressInfo(rdr.GetString(13), rdr.GetString(14), rdr.GetString(15), rdr.GetString(16), rdr.GetString(17), rdr.GetString(18), rdr.GetString(19), rdr.GetString(20), null, "email");
order = new OrderInfo(orderId, rdr.GetDateTime(0), rdr.GetString(1), null, billingAddress, shippingAddress, rdr.GetDecimal(21), null, null);
IList<LineItemInfo> lineItems = new List<LineItemInfo>();
LineItemInfo item = null;
//根據第一行及其子行創建商品列表
do ...{
item = new LineItemInfo(rdr.GetString(22), string.Empty, rdr.GetInt32(23), rdr.GetInt32(24), rdr.GetDecimal(25));
lineItems.Add(item);
} while (rdr.Read());
order.LineItems = new LineItemInfo[lineItems.Count];
lineItems.CopyTo(order.LineItems, 0);
}
}
return order;
}
/**//// <summary>
/// 獲取緩存參數的內部方法
/// </summary>
/// <returns></returns>
private static SqlParameter[] GetOrderParameters() ...{
SqlParameter[] parms = SqlHelper.GetCachedParameters(SQL_INSERT_ORDER);
if (parms == null) ...{
parms = new SqlParameter[] ...{
new SqlParameter(PARM_USER_ID, SqlDbType.VarChar, 80),
new SqlParameter(PARM_DATE, SqlDbType.DateTime, 12),
new SqlParameter(PARM_SHIP_ADDRESS1, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_ADDRESS2, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_CITY, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_STATE, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_ZIP, SqlDbType.VarChar, 50),
new SqlParameter(PARM_SHIP_COUNTRY, SqlDbType.VarChar, 50),
new SqlParameter(PARM_BILL_ADDRESS1, SqlDbType.VarChar, 80),
new SqlParameter(PARM_BILL_ADDRESS2, SqlDbType.VarChar, 80),
new SqlParameter(PARM_BILL_CITY, SqlDbType.VarChar, 80),
new SqlParameter(PARM_BILL_STATE, SqlDbType.VarChar, 80),
new SqlParameter(PARM_BILL_ZIP, SqlDbType.VarChar, 50),
&nb new SqlParameter(PARM_BILL_COUNTRY, SqlDbType.VarChar, 50),
new SqlParameter(PARM_TOTAL, SqlDbType.Decimal, 8),
new SqlParameter(PARM_BILL_FIRST_NAME, SqlDbType.VarChar, 80),
new SqlParameter(PARM_BILL_LAST_NAME, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_FIRST_NAME, SqlDbType.VarChar, 80),
new SqlParameter(PARM_SHIP_LAST_NAME, SqlDbType.VarChar, 80),
new SqlParameter(PARM_AUTHORIZATION_NUMBER, SqlDbType.Int)};
SqlHelper.CacheParameters(SQL_INSERT_ORDER, parms);
}
return parms;
}
private static SqlParameter[] GetItemParameters(int i) ...{
SqlParameter[] parms = SqlHelper.GetCachedParameters(SQL_INSERT_ITEM + i);
if (parms == null) ...{
parms = new SqlParameter[] ...{
new SqlParameter(PARM_LINE_NUMBER + i, SqlDbType.Int, 4),
new SqlParameter(PARM_ITEM_ID+i, SqlDbType.VarChar, 10),
new SqlParameter(PARM_QUANTITY+i, SqlDbType.Int, 4),
new SqlParameter(PARM_PRICE+i, SqlDbType.Decimal, 8)};
SqlHelper.CacheParameters(SQL_INSERT_ITEM + i, parms);
}
return parms;
}
}
好了,萬事俱備,只欠工廠了。這個項目就是傳說中的抽象工廠了,需要引用IDAL。
代碼非常簡單,如下:
好了,一個簡單的抽象工廠模式下的DAL層這裡就算建好了。我們在BusinessLogic層次中只需要引用DALFactory、IDAL、Model就可以使用了。直接使用DALFactory中的Create方法創建實例,然後調用其方法就可以實現對數據庫的增刪查改。
至於,在PetShop中還提供了的Message機制和Membership機制就下次再討論了。
/**//// <summary>
/// 抽象工廠類, 從配置文件創建DAL
/// </summary>
public sealed class DataAccess ...{
// 從配置文件獲取我們要使用的DAL類型
private static readonly string path = ConfigurationManager.APPSettings["WebDAL"];
private static readonly string orderPath = ConfigurationManager.APPSettings["OrdersDAL"];
private DataAccess() ...{ }
/**//// <summary>
/// 創建目錄訪問對象實例
/// </summary>
/// <returns>目錄接口實例</returns>
public static PetShop.IDAL.ICategory CreateCategory() ...{
//依照配置文件, 取得類名
string className = path + ".Category";
//反射調用, 根據數據庫不同創建不同對象
return (PetShop.IDAL.ICategory)Assembly.Load(path).CreateInstance(className);
}
/**//// <summary>
/// 創建存貨訪問對象實例
/// </summary>
/// <returns>存貨接口實例</returns>
public static PetShop.IDAL.IInventory CreateInventory() ...{
string className = path + ".Inventory";
return (PetShop.IDAL.IInventory)Assembly.Load(path).CreateInstance(className);
}
/**//// <summary>
/// 創建商品訪問對象實例
/// </summary>
/// <returns>商品接口實例</returns>
public static PetShop.IDAL.IItem CreateItem() ...{
string className = path + ".Item";
return (PetShop.IDAL.IItem)Assembly.Load(path).CreateInstance(className);
}
/**//// <summary>
/// 創建訂單訪問對象實例
/// </summary>
/// <returns>訂單接口實例</returns>
public static PetShop.IDAL.IOrder CreateOrder() ...{
string className = orderPath + ".Order";
return (PetShop.IDAL.IOrder)Assembly.Load(orderPath).CreateInstance(className);
}
/**//// <summary>
/// 創建產品訪問對象實例
/// </summary>
/// <returns>產品接口實例</returns>
public static PetShop.IDAL.IProduct CreateProduct() ...{
string className = path + ".Product";
return (PetShop.IDAL.IProduct)Assembly.Load(path).CreateInstance(className);
}
}