首页 > 代码库 > 项目实战SportsStore——订单处理模块

项目实战SportsStore——订单处理模块

前面的步骤如果顺利完成,你的网站运行之后应该能够正常显示下面三个页面:

1.产品列表

技术分享

2.购物车内容页面

在某个商品后面点击“添加到购物车”则出现下图页面:

技术分享

此页面上点击“继续购物”按钮则返回到产品列表页面(第一幅图所示)。

如果你的网站没有看到这个页面,你需要对照参考教材“8.3 提交订单”之前的内容进行修改。这里给大家提供一个参考代码,请点击下载

提交订单模块

1.添加订单相关数据表,记录订单信息

提交购物车中的订单,需要将用户购买的商品写到数据库里面保存起来,以后发货、用户查看订单等操作时,才知道哪个用户购买了哪些东西。所以,我们需要在数据库中建立“订单”数据表。

“订单”数据表中的每条记录应该需要记录:用户姓名、用户街道地址、用户所在城市、省份,额外我们还可以增加两个字段:是否需要给产品包装、是否需要送货上门(不用的话就让用户自己去提货)。其中用户地址这里,考虑到每个用户可能会有多个地址,我们允许用户添加多个地址,所以可以多增加两个字段来存放不同的地址。

所以我们可以按如下步骤扩展SportsStore数据库,来添加一个“订单”Orders数据表:

(1)打开Sql Server Management Studio,登录数据库服务器。

(2)点击“新建查询”按钮,打开SQL语句编辑器,如图:

技术分享

(3)输入SQL语句:

USE [SportsStore]GOCREATE TABLE [dbo].[Orders](    [OrderId] [int] IDENTITY(1,1) NOT NULL,    [Name] [nvarchar](max) NULL,    [Line1] [nvarchar](max) NULL,    [Line2] [nvarchar](max) NULL,    [Line3] [nvarchar](max) NULL,    [City] [nvarchar](max) NULL,    [State] [nvarchar](max) NULL,    [GiftWrap] [bit] NULL,    [Dispatched] [bit] NULL, CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderId] ASC))

(4)然后点击SSMS工具栏上的“执行”按钮(红色感叹号那个按钮),Orders数据表就创建好了。刷新一下数据库就可以在SportsStore里看到Orders表。

这里是使用SQL语句来创建数据库,当然你也可以使用图形化操作界面来添加一个数据表。

看到这里,你可能看到订单表里怎么没有有关产品的相关信息?一个订单有多个所购商品,上面“Orders”表包含了订单里除商品之外的相关信,对于订单里的所购商品,我们还需要再新增加一个数据表,这个表的每条记录应该包含如下信息:所购商品编号、所购订单编号、所购商品数量。不理解的同学,请自行脑补一下在学习数据库课程时,班级表和学生表之间的关系,这样就能理解订单表和所购商品表之间的关系了。

所购商品表的添加方法和订单表的添加方法是一样的,这里直接给出sql代码:

USE [SportsStore]GOCREATE TABLE [dbo].[OrderLines](    [OrderLinedId] [int] IDENTITY(1,1) NOT NULL,    [Quntity] [int] NOT NULL,    [Product_ProductID] [int] NULL,    [Order_OrderId] [int] NULL, CONSTRAINT [PK_OrderLines] PRIMARY KEY CLUSTERED ([OrderLinedId] ASC), CONSTRAINT [FK_OrderLines_Orders] FOREIGN KEY([OrderLinedId]) REFERENCES [dbo].[Orders] ([OrderId]), CONSTRAINT [FK_OrderLines_Products] FOREIGN KEY([Product_ProductID]) REFERENCES [dbo].[Products] ([ProductID]))

 

2.添加数据模型

程序读取数据库里的数据记录,需要将其转换成对象,才能够在我们的程序中方便地使用。仿照Product类,我们对Orders表和OrderLines表分别添加数据模型。

在项目的Models文件夹下,添加Order.cs文件,然后按如下代码修改文件内容:

Order.cs文件

 1 using System; 2 using System.Collections.Generic; 3  4 namespace SportsStore.Models 5 { 6  7     public class Order 8     { 9         public int OrderId { get; set; }10 11         public string Name { get; set; }12 13         public string Line1 { get; set; }14         public string Line2 { get; set; }15         public string Line3 { get; set; }16 17         public string City { get; set; }18 19         public string State { get; set; }20         public bool GiftWrap { get; set; }21         public bool Dispatched { get; set; }22         public virtual List<OrderLine> OrderLines { get; set; }23     }24 25     public class OrderLine26     {27         public int OrderLineId { get; set; }28         public Order Order { get; set; }29         public Product Product { get; set; }30         public int Quantity { get; set; }31     }32 }

可以看到在Orders.cs文件中添加了两个类:Order和OrderLine。看到这里,你至少要明白两点:

1)一个.cs文件里可以包含1个、2个甚至更多个不同的类。

2)数据模型类的文件名要和数据库相应数据表的名字一致(除了单复数有区别之外)。如Order类对应的是Orders数据表,OrderLine类对应的是OrderLines数据表。使用EF读取数据库时,它自动会将数据记录映射成Order对象或OrderLine对象。如果非要另起名字,Entity Framework框架就找不到哪个数据模型类对应哪个数据表了,只有额外再增加其它代码告诉EF它才能搞清楚对应关系。目前我们还是按默认一致的方式来给模型类命名。

注意第22行,这里定义了一个List<OrderLine>类型的属性,代表订单里所包含的所有已购商品。这个属性叫做导航属性,使用了导航属性之后,对于某个Order对象,其OrderLines属性里的数据就是与该Order相关的所有已购商品集合。导航属性能够让我们非常方便地读取数据库中具有一对一、一对多关系的数据表数据。但必须有一点需要注意,OrderLines表中必须要定义对Order表的外键约束。我们在前面定义OrderLines数据表时是添加了约束的,读者可以回头去仔细看一下创建OrderLines表的SQL代码。至于前面那个virtual关键字,表示的是“延迟加载”或“惰性加载”。有关导航属性的更多解释,请参考:关系与导航属性 

3.修改EF的上下文和存储库

数据表和模型类都有了,数据还是不能自己从数据库里跑到我们的程序里,还需要天假相关代码才可以实现数据库数据与我们程序之间的交互。

之前我们在实验中感受过使用ADO.NET的方式读写数据库,我们项目中不适用ADO.NET,而是使用EF组件。可以让我们编写最少的程序代码来解决复杂的问题。

使用EF,核心是上下文类。按如下步骤修改我们之前定义的EFDbContext类:

(1)打开Models->Repository文件夹下的EFDbContext.cs文件

(2)添加粗体字部分的那一行代码:

using System.Data.Entity;namespace SportsStore.Models.Repository{    public class EFDbContext:DbContext    {        public DbSet<Product> Products { get; set; }        public DbSet<Order> Orders { get; set; }    }}

我们在所有页面文件中不是直接使用这个EFDbContext类的,而是通过Repository类间接使用EFDbContext,所以还需要修改Repository类:

 1 using System.Collections.Generic; 2 using System.Data.Entity; 3 using System.Linq; 4  5 namespace SportsStore.Models.Repository 6 { 7     public class Repository 8     { 9         private EFDbContext context = new EFDbContext();10 11         public IEnumerable<Product> Products12         {13             get {14                 return context.Products;15             }16         }17         public IEnumerable<Order> Orders18         {19             get20             {21                 return context.Orders.Include(o => o.OrderLines.Select(ol => ol.Product));22             }23         }24 25         public void SaveOrder(Order order)26         {27             if (order.OrderId == 0)28             {29                 order = context.Orders.Add(order);30 31                 foreach (OrderLine line in order.OrderLines)32                 {33                     context.Entry(line.Product).State34                         = EntityState.Modified;35                 }36 37             }38             else39             {40                 Order dbOrder = context.Orders.Find(order.OrderId);41                 if (dbOrder != null)42                 {43                     dbOrder.Name = order.Name;44                     dbOrder.Line1 = order.Line1;45                     dbOrder.Line2 = order.Line2;46                     dbOrder.Line3 = order.Line3;47                     dbOrder.City = order.City;48                     dbOrder.State = order.State;49                     dbOrder.GiftWrap = order.GiftWrap;50                     dbOrder.Dispatched = order.Dispatched;51                 }52             }53             context.SaveChanges();54         }55     }56 }

第21句:这里的Include方法是Linq技术中的一个方法,表示加载关联实体,通常用在关联查询中,有关Inclue关联查询的用法,可以参考这篇博文。在我们的程序里,Order类有一个关联实体OrderLines,context.Orders使用了Include之后,表示在查询Orders数据表记录时,要把每条记录中所关联的OrderLine记录(在OrderLines表中)也从数据库里读出来。对于Select方法,其作用将结果进行投影。本来每个OrderLine对象会包含四个属性(见OrderLine类定义),使用了Select之后,只剩下Product有数据,其余属性的数据不需要映射了。有关Select的用法,请参考这篇博文

4.添加结算页面

用户在购物车页面——CartView.aspx中点击“结算”按钮,跳转到结算页面Checkout.aspx。下面是Checkout.aspx页面的代码内容:

<%@ Page Title="" Language="C#" MasterPageFile="~/Pages/Store.Master"AutoEventWireup="true" CodeBehind="Checkout.aspx.cs"       Inherits="SportsStore.Pages.Checkout" %><asp:Content ID="Content1" ContentPlaceHolderID="bodyContent" runat="server"><div id="content">    <div id="checkoutForm" class="checkout" runat="server">        <h2>Checkout Now</h2>            请输入你的详细信息,我们将尽快发货!        <div id="errors"  data-valmsg-summary="true">            <ul><li style="display:none"></li></ul>            <asp:ValidationSummary ID="ValidationSummary1" runat="server"/>        </div>        <h3>收货人姓名</h3>        <div>            <label for="name">姓名:</label>            <input Property="Name" runat="server" />        </div>        <h3>地址</h3>        <div>            <label for="line1">地址 1:</label>            <input Property="Line1" runat="server" />        </div>        <div>            <label for="line2">地址 2:</label>            <input Property="Line2" runat="server" />        </div>        <div>            <label for="line3">地址 3:</label>            <input Property="Line3" runat="server" />        </div>        <div>            <label for="city">城市:</label>            <input Property="City" runat="server" />        </div>        <div>            <label for="state">省:</label>            <input Property="State" runat="server" />        </div>        <h3>可选</h3>        <input type="checkbox" id="giftwrap" name="giftwrap" value=http://www.mamicode.com/"true"/>        是否包装所有商品?                <p class="actionButtons">            <button class="actionButtons" type="submit">完成订单</button>        </p>    </div>    <div id="checkoutMessage" runat="server">        <h2>谢谢!</h2>       感谢购买本店产品,我们已收到订单,将在最短时间内发货.        </div></div></asp:Content>

操作处理代码存放在Checkout.aspx.cs文件中:

using System;using System.Collections.Generic;using System.Web.ModelBinding;using SportsStore.Models;using SportsStore.Models.Repository;using SportsStore.Pages.Helpers;namespace SportsStore.Pages {    public partial class Checkout : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {            checkoutForm.Visible = true;            checkoutMessage.Visible = false;            if (IsPostBack) {                Order myOrder = new Order();                if (TryUpdateModel(myOrder,                   new FormValueProvider(ModelBindingExecutionContext))) {                    myOrder.OrderLines = new List<OrderLine>();                    Cart myCart = SessionHelper.GetCart(Session);                    foreach (CartLine line in myCart.Lines) {                        myOrder.OrderLines.Add(new OrderLine {                            Order = myOrder,                            Product = line.Product,                            Quantity = line.Quantity                        });                    }                    new Repository().SaveOrder(myOrder);                    myCart.Clear();                    checkoutForm.Visible = false;                    checkoutMessage.Visible = true;                }            }        }    }}

在购物车页面CartView.aspx中还需要增加一个按钮——“结算”按钮,用户点击按钮,就跳转到结算页面Checkout.aspx。要添加“结算”按钮,只需要在CartView.aspx页面中添加一行代码:

... <p class="actionButtons">     <a href=http://www.mamicode.com/"<%= ReturnUrl %>">Continue shopping</a>     <a href=http://www.mamicode.com/"<%= CheckoutUrl %>">Checkout</a> </p>...

粗体字部分就是要添加的内容。这里表示标签a的href属性需要调用CheckoutUrl属性才能获得其值。CHeckoutUrl属性被定义在CartView.aspx.cs文件中:

using System;using System.Collections.Generic;using SportsStore.Models;using SportsStore.Pages.Helpers;using SportsStore.Models.Repository;using System.Linq;using System.Web.Routing;namespace SportsStore.Pages {    public partial class CartView : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {            if (IsPostBack) {                Repository repo = new Repository();                int productId;                if (int.TryParse(Request.Form["remove"], out productId)) {                    Product productToRemove = repo.Products                        .Where(p => p.ProductID == productId).FirstOrDefault();                    if (productToRemove != null) {                        SessionHelper.GetCart(Session).RemoveLine(productToRemove);                    }                }            }        }        public IEnumerable<CartLine> GetCartLines() {            return SessionHelper.GetCart(Session).Lines;        }        public decimal CartTotal {            get {                return SessionHelper.GetCart(Session).ComputeTotalValue();            }        }        public string ReturnUrl {            get {                return SessionHelper.Get<string>(Session, SessionKey.RETURN_URL);            }        }        public string CheckoutUrl {            get {                return RouteTable.Routes.GetVirtualPath(null, "checkout",                    null).VirtualPath;            }        }    }}

 

项目实战SportsStore——订单处理模块