65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 NHibernate 进行对象关系映射 (ORM) - 第 7 部分(共 8 部分)完成电子商务示例

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2012年11月15日

CPOL

7分钟阅读

viewsIcon

28945

downloadIcon

2694

包含 8 部分的完整系列文章,展示使用 NHibernate 进行一对一、多对一、多对多关联映射,使用 NHibernate 处理集合,使用 NHibernate 处理继承关系,使用 NHibernate 处理延迟初始化/获取。

文章系列

引言

在第 1-6 篇文章中,我们完成了实体类之间的各种关联类型以及实体类之间的继承。用于说明这些思想的示例一直是电子商务场景。我们已经达到了需要发货产品的阶段。在本第七篇文章及其客户端代码中,我们将演示我们为实现发货功能而提出的类将允许一个订单的发货项可以通过多个发货服务分批发货。这种灵活性提供了业务优势。也可能出现同一客户的多个订单可以一次发货。这一切之所以成为可能,是因为我们将 ShippingPaymentApprovedOrder 实体之间的关系建模为与 OrderShipment 作为关联类的多对多关联。本文及客户端代码还将演示我们添加 ShipmentItem(即已付款订单要发货的商品)的方式将捕获电子商务领域中的物料移动。

背景

所有必需的背景知识已在第 1-6 篇文章中涵盖。

注意:包含的两个项目是 ECommerce1 和 ECommerce 2。ECommerce1 用于第 1-3 篇文章的示例,Ecommerce 2 用于本第 7 篇文章之前的所有其余示例。还提供了用于测试每篇文章中的类及其关联的客户端项目,并且每篇文章的测试代码都命名清晰,例如“第 1 篇文章的测试代码”等。在开始后续文章时,前一篇文章的测试代码将在测试客户端项目中被注释掉,并且主项目中的类将已经演变并更改以适应下一篇文章的后续场景。已注释掉的前一篇文章的测试代码尚未更新以反映对类的更改。因此,测试代码将能为每个项目的最新文章正确编译和工作,即 ECommerce1 的第 3 篇文章,以及 Ecommerce 2 的第 7 篇文章和第 6 篇文章,这完成了我们的电子商务示例。请遵循之前关于使用 Microsoft Visual Studio 2012 的项目的说明,尽管我们都知道如何无需帮助就能完成。

使用代码

电子商务场景的代码示例

在这里,我们将简要解释并完成 ShipmentItem 场景,并演示引言中阐述的要点。场景是:一旦客户向电子商务网站下了已付款订单,并选择了他偏好的发货方式,电子商务网站的发货部门将处理订单并向客户完成已付款订单中每件商品的配送。

因此,当客户创建已付款订单时,客户会选择快速发货或包裹服务,根据这些选择,会创建 FastShippingParcelService 的实例,并存储在基类引用 Shipping 中。此 Shipping 引用存储了其所属的已付款订单 ID(在 PrimaryPaidOrderId 属性中),并持久化到数据库。它不是外键,只是一个属性(因为 ShippingPaymentApprovedOrder 通过关联类 OrderShipment 绑定)。当发货部门的人员需要输入发货信息时,他们会输入此主要已付款订单 ID (PRIMARYPAIDORDERID),以便进行特定发货实例的发货。他们如何知道这个 PRIMARYPAIDORDERID

这是尚未发货的已付款订单 ID(即新订单的 ID - PAYMENTAPPROVEDORDERID),该 ID 可供应用程序从列表中检索并显示,发货部门可以从中选择一个。因此,利用这一点,可以从存储库中检索订单创建时(由客户创建)的该主要已付款订单 ID 的特定发货实例,并向其中添加发货信息,然后再次持久化到存储库。如果发货部门决定分批发货,即一个订单进行多次发货,那么对于后续批次,他们无需添加主要已付款订单 ID。他们只需创建新的 Shipping 实例并提供所有发货信息。OrderShipment 实例无论如何都会关联 ShippingPaymentApprovedOrder。这种设计将为业务场景提供最大的灵活性。

ECommerceSellerSystem 中 AddShippingInformation 的代码是

public Shipping AddShippingInformation(bool is_primary, bool is_fast, 
  long prime_id, string shipping_name, DateTime shipping_start_date, 
  string tracking_number,bool is_gift_wrapped)
{
    Shipping shipping = null;
    IRepository<Shipping> shipping_repo = new DBRepository<Shipping>();
    NHibernate.ISession session = null;
    NHibernateHelper nh = NHibernateHelper.Create();
    session = nh.Session;    
    if (is_primary)
    {
        string query_string = 
          "from Shipping shipping1 where shipping1.PrimaryPaidOrderId = :prime_id";
        shipping = session.CreateQuery(query_string).SetParameter<long>(
                     "prime_id",prime_id).UniqueResult<Shipping>();
        if (shipping != null)
        {
            shipping.ShippingName = shipping_name;
            shipping.ShippingStartDate = DateTime.Today;
            if (shipping is FastShipping)
                ((FastShipping)shipping).TrackingNumber = tracking_number;
        }
    }
    else
    {
        if (is_fast)
        {
            shipping = new FastShipping { ShippingName=shipping_name, 
              ShippingStartDate=DateTime.Today, PrimaryPaidOrderId=null, 
              TrackingNumber=tracking_number };
        }
        else
        {
            shipping = new ParcelService { ShippingName = shipping_name, 
              ShippingStartDate = DateTime.Today, 
              PrimaryPaidOrderId = null, IsGiftWrapped = is_gift_wrapped };
        }
    }
    if (shipping != null)
    {
        using (NHibernate.ITransaction trans = session.BeginTransaction())
        {
            session.SaveOrUpdate(shipping);
            trans.Commit();
        }
    }
    return shipping;
}

如前所述,ShippingPaymentApprovedOrder 之间的多对多关联的关联类是 OrderShipment。每个 OrderShipment 实例还将包含客户订购并在该次发货中发货的商品列表,即 ShipmentItem 的集合。但是,如果电子商务网站的发货部门决定分批发送某个订单的商品,即先发送一部分商品到一个发货实例,剩余的发送到其他发货实例,那么将相应地创建多个 ShippingOrderShipment 实例,每个 OrderShipment 实例将包含在此次发货中发货的商品项的集合。ECommerceSellerSystem 中 AddingShipmentItem 的代码是

public void AddShipmentItem(OrderShipment order_shipment, string inventory_serial_code)
{
    //The barcode scanner returns a string of inventory serial code.
    //Item instance will have to be retrieved using serial code.
    //Item in Inventory will have to be deleted inside Transaction.
    //In the same transaction, the item has to be added to inventory.
    NHibernateHelper nh = NHibernateHelper.Create();
    using (NHibernate.ISession session = nh.Session)
    {
        string query_string = "from Item item1 where item1.InventorySerialCode = :code";
        Item inventory_item = session.CreateQuery(query_string).SetParameter<string>(
             "code", inventory_serial_code).UniqueResult<Item>();
        if (inventory_item != null)
        {
            IRepository<Item> item_repository = new DBRepository<Item>();
            IRepository<OrderShipment> order_shipment_repository = new DBRepository<OrderShipment>();
            using (NHibernate.ITransaction trans = session.BeginTransaction())
            {
                inventory_item.PaidOrder.PaidOrderItems.Remove(inventory_item);
                session.Delete(inventory_item);
                order_shipment.ShipmentItems.Add(
                  new ShipmentItem { InventorySerialCode = inventory_item.InventorySerialCode });
                session.SaveOrUpdate(order_shipment);
                trans.Commit();
            }
        }
    }
}

关于上述代码片段,需要注意的重要一点是如何捕获领域中的物料移动。当一件商品被订购并发货后,它就不再存在于商品库存中。上面编写的代码准确地捕获了领域。商品从 ITEM 表表示的商品库存移动到 SHIPMENTITEMS 表。ITEM 表中的一个标志就足够了,但是将已售出和已发货的商品从 ITEM 表中移除并将其移动到 SHIPMENTITEMS 表中,使得任何审查更容易,并增加了清晰度。用于测试的客户端代码如下:

//Add Shipping Information - NOTE THAT THESE METHODS ARE CALLED WITHIN DEPARTMENTS OF ECOMMERCE SITE
//Shipping information for PAYMENTAPPROVEDORDER
ECommerceSellerSystem seller_system3 = new ECommerceSellerSystem();
//SHIPPING DEPARTMENT SELCTS A APPROPRIATE SHIPPING AGENCY AND  CREATES A SHIPPING FOR THE PRODUCT
Shipping shipping1 = seller_system3.AddShippingInformation(true, true, 
  pay_order1.PaymentApprovedOrderId, "Noble House Ships", 
  DateTime.Today, "A101A101", false);
//THE ORDER SHIPMENT BEGINS
OrderShipment order_shipment1 = seller_system3.CreateOrderShipment(pay_order1, shipping1);
//THE SHIPPING MANAGER, USES A BARCODE SCANNER TO SCAN A ITEM TO BE SHIPPED
//BARCODE SCANNER JUST GIVES A TEXT IDENTIFYING THE PRODUCT - HERE 00A0110
seller_system3.AddShipmentItem(order_shipment1, "00A0110");
//THE SHIPPING MANAGER, USES A BARCODE SCANNER TO SCAN A ITEM TO BE SHIPPED
//BARCODE SCANNER JUST GIVES A TEXT IDENTIFYING THE PRODUCT - HERE "01A0101"
seller_system3.AddShipmentItem(order_shipment1, "01A0101");
//FOR THE SAME ORDER A DIFFERENT SHIPPING IS SELECTED - THIS FLEXIBILITY IS GOOD BECAUSE IT ALLOWS
//THE SHIPPING DEPARTMENT TO GIVE ITEMS OF SAME ORDER
// TO DIFFERENT SHIPPERS EACH SPECIALISING IN THAT ITEM TYPE
Shipping shipping2 = seller_system3.AddShippingInformation(false, true, 
  pay_order1.PaymentApprovedOrderId, "Fast Service", DateTime.Today, "A10101A", false);   
//AS BEFORE THE SHIPPINGMANAGER ADDS ITEMS TO SHIP
OrderShipment order_shipment2 = seller_system3.CreateOrderShipment(pay_order1, shipping2);
seller_system3.AddShipmentItem(order_shipment2, "02A10101");
seller_system3.AddShipmentItem(order_shipment2, "03A01010");
seller_system3.AddShipmentItem(order_shipment2, "04A101010");
//WHEN A ITEM IS PICKED FROM INVENTORY TO BE SHIPPED BY SHIPPING MANAGER USING THE ABOVE METHOD,
//IT CEASES TO LIVE IN INVENTORY's ITEM TABLE AND IS MOVED TO SHIPMENTITEMS TABLE TO CAPTURE
//MATERIAL MOVEMENT IN DOMAIN MAKING IT EASIER FOR SCRUTINY LATER.

下面的图 1 显示了客户端代码创建的表。最上面是 SHIPMENTITEMS 表,中间是 SHIPPING 表,最下面是 ITEM 表。请注意,SHIPPING 表(中间表)的第二行有 null 值。之所以有 null 值,是因为我们在客户端代码中没有为其提供发货信息,并且该行是在客户按照第 6 篇文章创建订单时创建的。将其与第 1 行进行比较,我们已在上面的客户端代码中提供了该行的所有发货信息。另请注意,在 SHIPPING 表(中间)中,对于 SHIPPINGID = 3 的第三行,PRIMARYPAIDORDERID 为 null,因为此行不是客户创建已付款订单时创建的。此行由电子商务网站的发货部门创建,该部门决定将 PAYMENTAPPROVEDORDER=1 分两次发货,因此通过提供发货信息来添加此发货实例。如前所述,PRIMARYPAIDODERID 仅在客户在创建已付款订单时提供发货偏好时才由应用程序填充。另请注意,在顶部的 SHIPMENTITEMS 表中,所有商品都属于同一个已付款订单,但属于两个不同的发货实例,每个实例代表一个不同的发货公司(请参阅最顶部的 SHIPMENTITEM 表中的 ShippingID 列和 PAYMENTAPPROVEDORDERID 列)。这就是我们之前提到的灵活性。另请注意,当这些发货商品项移动到 SHIPMENTITEMS 表时,它们就不再存在于库存中,即 ITEM 表(图 1 的底部表)中。

图 1 - 顶部 - SHIPMENTITEMS 表,中间 - SHIPPING 表,底部 - ITEM 表

关注点

结论

示例的设计牢记两个核心原则:简单(KISS)和最小化(YAGNI)。实体类在保持高内聚性方面进行了重构,但显然它们不能具有低耦合性,因为整个文章系列是为了解释各种实体关联,并且在大部分情况下使用了双向关联来阐明思想。本文完成了电子商务示例的各种场景。本系列文章的下一篇将讨论 NHibernate 中的延迟加载功能。享受 NHibernate。

© . All rights reserved.