一个合格的项目,最基本之一就是合理的设计数据表与实体类,有些刚刚入门,很难理清数据之间的关系,使得数据表设计得非常不合理;So,我也总结一下,记录下来,也希望对有迷惑的朋友有帮助。
首先,是Model类的设计(ORM使用了EF,Codefirst模式生成的数据表,这里为了方便, 遵循了EF命名约定,就不用再去写配置了);
1、Room(学生宿舍)
public class Room
{
[Key]
public int Id { get; set; }
public string Number { get; set; }
public string Building { get; set; }
public int MaxPerson { get; set; }
public virtual ICollection<Student> Persons { get; set; }
}
2、Student(学生)
public class Student
{
[Key]
public int Id { get; set; }
public string Number { get; set; }
public string Name { get; set; }
/// <summary>
/// 是否住校
/// </summary>
public bool UseRoom { get; set; } = true;
public int? RoomId { get; set; }
public Room Room { get; set; }
}
3、Product(商品,说明:为了讲解,没有电商SKU的概念了,就先把他看成一个实际的商品)
public class Product
{
[Key]
public int Id { get; set; }
public string Number { get; set; }
public string Name { get; set; }
public double Weight { get; set; }
public decimal Price { get; set; }
/// <summary>
/// 生产日期
/// </summary>
public DateTime ProductDate { get; set; }
/// <summary>
/// 有效期至
/// </summary>
public DateTime ValidDate { get; set; }
}
4、Order(订单)
public class Order
{
[Key]
public int Id { get; set; }
/// <summary>
/// 订单编号,业务主键,唯一性
/// </summary>
public string Number { get; set; }
public int BuyerId { get; set; }
/// <summary>
/// 订单的客户信息
/// </summary>
public Student Buyer { get; set; }
/// <summary>
/// 住宿舍的话,,如A栋110号房间;否则手动输入
/// </summary>
public string BuyerAddress{get;set;}
/// <summary>
/// 订单的商品信息
/// </summary>
public virtual ICollection<OrderItem> ProductItems { get; set; }
/// <summary>
/// 订单的总价
/// </summary>
public decimal Account { get; set; }
}
5、OrderItem(订单明细)
public class OrderItem
{
[Key]
public int Id { get; set; }
/// <summary>
/// 属于哪一个订单,注意:是一个,与订单的关系是1:N,而且是必须属于某个订单
/// </summary>
public Order Order { get; set; }
public int OrderId { get; set; }
public int ProductId { get; set; }
/// <summary>
/// 商品
/// </summary>
public Product Product { get; set; }
/// <summary>
/// 数量
/// </summary>
public int Qty { get; set; }
/// <summary>
/// 单价,为什么用又拿一个字段出来记录?
/// 1、商品的价格是变动的,而客户下单时的价格是按'现在的商品价格'计算;
/// 2、个人的话说,订单的价格具有时效性
/// 已此可理解了,Order里客户的地址字段也是同理
/// </summary>
public decimal Price { get; set; }
}
整理一下关系:
Student与Room:一般现实情况是,一个学生只能住一个宿舍,反过来,一个宿舍可以住多个学生;但有这种情况,有的学生家比较近,不住宿舍,所以:一个学生可以有且最多有1个宿舍(高中数学的味道了),关系为0:N;
Order与OrderItem:订单明细必须属于一个订单,不可能脱离订单存在,其实他们是一个整体,但如果把他们的字段放到一张表里面,想想有多少冗余数据啊,所以一定是1:N;
Student与Order:首先,我这里模拟的是校园内学生点餐场景,所以把学生当做客户。一个订单,必须有客户,且只有一个客户,反过来,客户可以下多个订单,一般的数据表设计,你用我,你就记录我,这是外键的通俗理解,所以也是1:N;
OrderItem与Product:一般的,一条订单明细代表一件商品,订单明细必须指定商品,1:N;
再来看看对应数据表的设计
说明:
1、Id主键、Number编码(编号);
2、OrderItem的Price就是Product的Price数据,下订单就是商品当前的价格,就是按当前价格计算总价,比如你买了2件商品,3天到货,不满意、退一件,这时候这件商品的价格已经发生变化,不可能按现在的价格退,是吧。还是按买的时候的价格来退的,所以这个不是冗余字段,是业务场景下必须的,同理还有Order里的BuyerAddress;
3、个人不推荐用外键,所以把EF自动生成的字段、外键等删了;不影响导航属性的映射,上面说了,遵循了EF的命名规范。据了解,大系统一般是不使用物理外键的,而是提到程序代码中去验证;提交之前去验证导航属性,不仅仅是验证外键是否存在,还有业务逻辑上的,比如场景:B员工填写一个派车申请单,选择的车辆的状态为正常的A1234车(业务需求是只能选正常车哦),估计1分钟之后提交,在选择了车之后与提交之前的这一分钟里,A1234车的司机把车辆的状态改为故障,之后B员工提交上来的派车申请单,也就不仅仅外键验证数据的问题了;这样的业务还是需要设计程序人员多多思考的;
现在,来个控制台添加数据测试吧
static void Main(string[] args)
{
MyDbContext dbContext = new MyDbContext();
//Room r = new Room { Number = "101", Building = "A栋", MaxPerson = 8 };
//(r);
//();
Room r = ();
#region Student Add
//List<Student> stuList = new List<Student>();
//for (int i = 0; i < 5; i++)
//{
// (new Student { Name = "Jack" + i, Number = () + "_" + i, RoomId = });
//}
//(stuList);
//();
#endregion
#region Product Add
//List<Product> products = new List<Product> {
// new Product { Name="西瓜三明治", Number="smz001", Price=3, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight= },
// new Product { Name="鸡腿汉堡", Number="hb001", Price=5, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight= },
// new Product { Name="八宝粥", Number="bbz001", Price=6, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight= },
// new Product { Name="蛋炒饭", Number="dcf001", Price=8, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight= },
//};
//(products);
#endregion
#region 模拟购物订单
using (var tran = ())
{
try
{
//客户 Jack2
Student buyer = (s => s.Name == "Jack2").FirstOrDefault();
//买 八宝粥1份、蛋炒饭2份
Product[] items = (a => a.Name == "八宝粥" || a.Name == "蛋炒饭").ToArray();
Order order = new Order
{
BuyerId = buye,
//校园点餐,不住宿舍的,手输送货地点(校园内的)
BuyerAddress = buyer.UseRoom ? buyer.Room.Building + buyer.Room.Number : "篮球场第1个垃圾桶旁",
Number = () + "-" + buye,
ProductItems = new List<OrderItem> {
new OrderItem { ProductId=items[0].Id, Price=items[0].Price, Qty=1},
new OrderItem { ProductId=items[1].Id, Price=items[1].Price, Qty=2},
},
//订单合计
Account = items[0].Price * 1 + items[1].Price * 2,
};
order = (order);
();
(orde);
();
}
catch (Exception ex)
{
();
throw ex;
}
}
#endregion
}
数据库表效果
Ok,基本上的思路就是这样。