解读Petshop3.2用Nhibernate重构系列(四)
这一节,我们来讲述如何实现一个一对多对象的操作。很显然的,在PetShop中最显著的就是Order和LineItem之间的关系了。
我们应该把Order对象和LineItem对象的保存处理在一个事务中。
在这一节,他利用了通过一个回调函数提供一个事务环境(TransHelper.cs)。
首先我们看看这些配置文件
包含了属性/组件/bag :)我只是知道大概,但是不知道怎么表达,没什么仔细研究:)
最重要的是bag--它可以用来指定一个一对多的关系。
接着我们来看接口
我们在这里可以看到对于这种操作,他得通过一个回调函数来显式的控制事务。
这样我们就可以不用考虑那么多了,只需要考虑操作步骤。
1.给Order对象的Status赋值
2.通过对象工厂获取一个OrderDAO的借口,并执行新增Order操作。
在这个步骤,Order LineItem OrderStatus被执行了插入操作。
3.我们需要扣除已经销售的库存
我们遍历LineItems属性,调用对应的扣除库存的操作
完成了。其实也不难,只是不知道方法而已:)
我们应该把Order对象和LineItem对象的保存处理在一个事务中。
在这一节,他利用了通过一个回调函数提供一个事务环境(TransHelper.cs)。
首先我们看看这些配置文件
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="PetShop.BLL.Order, PetShop.BLL" table="Orders">
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0" access="nosetter.camelcase-underscore">
<generator class="native"/>
</id>
<property type="String" length="20" name="UserId" column="UserId" not-null="true"/>
<property type="DateTime" not-null="true" column="OrderDate" name="Date"/>
<component name="CreditCard" class="PetShop.BLL.CreditCard, PetShop.BLL">
<property name="CardNumber" type="String" length="20" column="CreditCard" not-null="true"/>
<property name="CardExpiration" column="ExprDate" type="String" length="7" not-null="true"/>
<property name="CardType" column="CardType" length="40" not-null="true" type="String"/>
</component>
<component name="BillingAddress" class="PetShop.BLL.Address, PetShop.BLL">
<property name="Address1" column="BillAddr1" type="String" length="80" not-null="true"/>
<property name="Address2" column="BillAddr2" type="String" length="80" not-null="false"/>
<property name="City" column="BillCity" type="String" length="80" not-null="true"/>
<property name="State" column="BillState" type="String" length="80" not-null="true"/>
<property name="Zip" column="BillZip" type="String" length="20" not-null="true"/>
<property name="Country" column="BillCountry" type="String" length="20" not-null="true"/>
<property name="FirstName" column="BillToFirstName" type="String" length="80" not-null="true"/>
<property name="LastName" column="BillToLastName" type="String" length="80" not-null="true"/>
</component>
<component name="ShippingAddress" class="PetShop.BLL.Address, PetShop.BLL">
<property name="Address1" column="ShipAddr1" type="String" length="80" not-null="true"/>
<property name="Address2" column="ShipAddr2" type="String" length="80" not-null="false"/>
<property name="City" column="ShipCity" type="String" length="80" not-null="true"/>
<property name="State" column="ShipState" type="String" length="80" not-null="true"/>
<property name="Zip" column="ShipZip" type="String" length="20" not-null="true"/>
<property name="Country" column="ShipCountry" type="String" length="20" not-null="true"/>
<property name="FirstName" column="ShipToFirstName" type="String" length="80" not-null="true"/>
<property name="LastName" column="ShipToLastName" type="String" length="80" not-null="true"/>
</component>
<property name="OrderTotal" type="Decimal" column="TotalPrice" not-null="true"/>
<property name="Courier" type="String" column="Courier" not-null="true" length="80" access="field.camelcase-underscore"/>
<property name="Locale" type="String" column="Locale" not-null="true" length="20" access="field.camelcase-underscore"/>
<bag name="LineItems" access="nosetter.camelcase-underscore" inverse="true" cascade="save-update">
<key column="OrderId"/>
<one-to-many class="PetShop.BLL.CartItem, PetShop.BLL"/>
</bag>
<one-to-one name="Status" class="PetShop.BLL.OrderStatus, PetShop.BLL" cascade="save-update"/>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="PetShop.BLL.Order, PetShop.BLL" table="Orders">
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0" access="nosetter.camelcase-underscore">
<generator class="native"/>
</id>
<property type="String" length="20" name="UserId" column="UserId" not-null="true"/>
<property type="DateTime" not-null="true" column="OrderDate" name="Date"/>
<component name="CreditCard" class="PetShop.BLL.CreditCard, PetShop.BLL">
<property name="CardNumber" type="String" length="20" column="CreditCard" not-null="true"/>
<property name="CardExpiration" column="ExprDate" type="String" length="7" not-null="true"/>
<property name="CardType" column="CardType" length="40" not-null="true" type="String"/>
</component>
<component name="BillingAddress" class="PetShop.BLL.Address, PetShop.BLL">
<property name="Address1" column="BillAddr1" type="String" length="80" not-null="true"/>
<property name="Address2" column="BillAddr2" type="String" length="80" not-null="false"/>
<property name="City" column="BillCity" type="String" length="80" not-null="true"/>
<property name="State" column="BillState" type="String" length="80" not-null="true"/>
<property name="Zip" column="BillZip" type="String" length="20" not-null="true"/>
<property name="Country" column="BillCountry" type="String" length="20" not-null="true"/>
<property name="FirstName" column="BillToFirstName" type="String" length="80" not-null="true"/>
<property name="LastName" column="BillToLastName" type="String" length="80" not-null="true"/>
</component>
<component name="ShippingAddress" class="PetShop.BLL.Address, PetShop.BLL">
<property name="Address1" column="ShipAddr1" type="String" length="80" not-null="true"/>
<property name="Address2" column="ShipAddr2" type="String" length="80" not-null="false"/>
<property name="City" column="ShipCity" type="String" length="80" not-null="true"/>
<property name="State" column="ShipState" type="String" length="80" not-null="true"/>
<property name="Zip" column="ShipZip" type="String" length="20" not-null="true"/>
<property name="Country" column="ShipCountry" type="String" length="20" not-null="true"/>
<property name="FirstName" column="ShipToFirstName" type="String" length="80" not-null="true"/>
<property name="LastName" column="ShipToLastName" type="String" length="80" not-null="true"/>
</component>
<property name="OrderTotal" type="Decimal" column="TotalPrice" not-null="true"/>
<property name="Courier" type="String" column="Courier" not-null="true" length="80" access="field.camelcase-underscore"/>
<property name="Locale" type="String" column="Locale" not-null="true" length="20" access="field.camelcase-underscore"/>
<bag name="LineItems" access="nosetter.camelcase-underscore" inverse="true" cascade="save-update">
<key column="OrderId"/>
<one-to-many class="PetShop.BLL.CartItem, PetShop.BLL"/>
</bag>
<one-to-one name="Status" class="PetShop.BLL.OrderStatus, PetShop.BLL" cascade="save-update"/>
</class>
</hibernate-mapping>
最重要的是bag--它可以用来指定一个一对多的关系。
接着我们来看接口
using System;
//References to PetShop specific libraries
//PetShop busines entity library
using PetShop.BLL;
namespace PetShop.IDAO{
/// <summary>
/// Interface for the Order DAL
/// </summary>
public interface IOrderDAO{
/// <summary>
/// Method to insert an order header
/// </summary>
/// <param name="order">Business entity representing the order</param>
void Insert(Order order);
/// <summary>
/// Reads the order information for a given orderId
/// </summary>
/// <param name="orderId">Unique identifier for an order</param>
/// <returns>Business entity representing the order</returns>
Order GetOrder(int orderId);
}
}
//References to PetShop specific libraries
//PetShop busines entity library
using PetShop.BLL;
namespace PetShop.IDAO{
/// <summary>
/// Interface for the Order DAL
/// </summary>
public interface IOrderDAO{
/// <summary>
/// Method to insert an order header
/// </summary>
/// <param name="order">Business entity representing the order</param>
void Insert(Order order);
/// <summary>
/// Reads the order information for a given orderId
/// </summary>
/// <param name="orderId">Unique identifier for an order</param>
/// <returns>Business entity representing the order</returns>
Order GetOrder(int orderId);
}
}
这里定义了新增订单/根据订单ID获取订单的操作规范。
同时我们还需要看看IInventoryDAO接口
using System;
using PetShop.BLL;
namespace PetShop.IDAO{
/// <summary>
/// Interface for the Inventory DAL
/// </summary>
public interface IInventoryDAO{
/// <summary>
/// Reduces the stock level by the given quantity for items in an order
/// </summary>
/// <param name="inventory"></param>
void TakeStock(Inventory inventory);
}
}
using PetShop.BLL;
namespace PetShop.IDAO{
/// <summary>
/// Interface for the Inventory DAL
/// </summary>
public interface IInventoryDAO{
/// <summary>
/// Reduces the stock level by the given quantity for items in an order
/// </summary>
/// <param name="inventory"></param>
void TakeStock(Inventory inventory);
}
}
这个定义了减少库存的操作。
那么,接下来,我们可以来看看实体类
using System;
using System.Collections;
using PetShop.Helper;
using PetShop.IDAO;
namespace PetShop.BLL{
/// <summary>
/// Business entity used to model an order
/// </summary>
[Serializable]
public class Order{
// These variables are used to demonstrate the rollback characterisitic
// of distributed transactions and would not form part of a production application
private const string ACID_USER_ID = "ACID";
private const string ACID_ERROR_MSG = "ACID test exception thrown for distributed transaction!";
private int _orderId;
private DateTime _date;
private string _userId;
private CreditCard _creditCard;
private Address _billingAddress;
private Address _shippingAddress;
private decimal _orderTotal;
private string _courier = "UPS";
private string _locale = "US_en";
private IList _lineItems;
private OrderStatus _status;
public int OrderId{
get { return _orderId; }
}
public DateTime Date{
get { return _date; }
set { _date = value; }
}
public string UserId{
get { return _userId; }
set { _userId = value; }
}
public CreditCard CreditCard{
get { return _creditCard; }
set { _creditCard = value; }
}
public Address BillingAddress{
get { return _billingAddress; }
set { _billingAddress = value; }
}
public Address ShippingAddress{
get { return _shippingAddress; }
set { _shippingAddress = value; }
}
public decimal OrderTotal{
get { return _orderTotal; }
set { _orderTotal = value; }
}
public IList LineItems {
get { return _lineItems; }
set {
int lineNum = 1;
foreach(CartItem item in value){
item.LineNum = lineNum++;
item.Order = this;
}
_lineItems = value;
}
}
public OrderStatus Status {
get { return _status; }
set {
_status = value;
_status.Order = this;
}
}
public static Order Load(int orderId) {
// Validate input
if (orderId < 1) return null;
// Return the order from the DAL
return ((IOrderDAO)ObjectFactory.GetInstance("OrderDAO")).GetOrder(orderId);
}
public void Insert(){
IManagedTransactionContext mtc = new TransHelper();
mtc.DoCallback( new ContextCallback(InsertInTransaction) );
}
/// <summary>
/// A method to insert a new order into the system
/// The orderId will be generated within the method and should not be supplied
/// As part of the order creation the inventory will be reduced by the quantity ordered
/// </summary>
/// <param name="order">All the information about the order</param>
private void InsertInTransaction(){
// Call the insert method in the DAL to insert the header
this.Status = new OrderStatus();
((IOrderDAO)ObjectFactory.GetInstance("OrderDAO")).Insert(this);
foreach(CartItem item in LineItems){
item.TakeStock();
}
// As part of the sample application we have created a user
// you can tested distributed transactions with
// If the order has been created with the user 'Acid',
// then throw an exception which will rollback the entire transaction
if (this.UserId == ACID_USER_ID) throw new ApplicationException(ACID_ERROR_MSG);
}
}
}
using System.Collections;
using PetShop.Helper;
using PetShop.IDAO;
namespace PetShop.BLL{
/// <summary>
/// Business entity used to model an order
/// </summary>
[Serializable]
public class Order{
// These variables are used to demonstrate the rollback characterisitic
// of distributed transactions and would not form part of a production application
private const string ACID_USER_ID = "ACID";
private const string ACID_ERROR_MSG = "ACID test exception thrown for distributed transaction!";
private int _orderId;
private DateTime _date;
private string _userId;
private CreditCard _creditCard;
private Address _billingAddress;
private Address _shippingAddress;
private decimal _orderTotal;
private string _courier = "UPS";
private string _locale = "US_en";
private IList _lineItems;
private OrderStatus _status;
public int OrderId{
get { return _orderId; }
}
public DateTime Date{
get { return _date; }
set { _date = value; }
}
public string UserId{
get { return _userId; }
set { _userId = value; }
}
public CreditCard CreditCard{
get { return _creditCard; }
set { _creditCard = value; }
}
public Address BillingAddress{
get { return _billingAddress; }
set { _billingAddress = value; }
}
public Address ShippingAddress{
get { return _shippingAddress; }
set { _shippingAddress = value; }
}
public decimal OrderTotal{
get { return _orderTotal; }
set { _orderTotal = value; }
}
public IList LineItems {
get { return _lineItems; }
set {
int lineNum = 1;
foreach(CartItem item in value){
item.LineNum = lineNum++;
item.Order = this;
}
_lineItems = value;
}
}
public OrderStatus Status {
get { return _status; }
set {
_status = value;
_status.Order = this;
}
}
public static Order Load(int orderId) {
// Validate input
if (orderId < 1) return null;
// Return the order from the DAL
return ((IOrderDAO)ObjectFactory.GetInstance("OrderDAO")).GetOrder(orderId);
}
public void Insert(){
IManagedTransactionContext mtc = new TransHelper();
mtc.DoCallback( new ContextCallback(InsertInTransaction) );
}
/// <summary>
/// A method to insert a new order into the system
/// The orderId will be generated within the method and should not be supplied
/// As part of the order creation the inventory will be reduced by the quantity ordered
/// </summary>
/// <param name="order">All the information about the order</param>
private void InsertInTransaction(){
// Call the insert method in the DAL to insert the header
this.Status = new OrderStatus();
((IOrderDAO)ObjectFactory.GetInstance("OrderDAO")).Insert(this);
foreach(CartItem item in LineItems){
item.TakeStock();
}
// As part of the sample application we have created a user
// you can tested distributed transactions with
// If the order has been created with the user 'Acid',
// then throw an exception which will rollback the entire transaction
if (this.UserId == ACID_USER_ID) throw new ApplicationException(ACID_ERROR_MSG);
}
}
}
这样我们就可以不用考虑那么多了,只需要考虑操作步骤。
1.给Order对象的Status赋值
2.通过对象工厂获取一个OrderDAO的借口,并执行新增Order操作。
在这个步骤,Order LineItem OrderStatus被执行了插入操作。
3.我们需要扣除已经销售的库存
我们遍历LineItems属性,调用对应的扣除库存的操作
完成了。其实也不难,只是不知道方法而已:)