1. 程式人生 > 實用技巧 >C#.NET 實體與資料庫表的設計思路

C#.NET 實體與資料庫表的設計思路

一個合格的專案,最基本之一就是合理的設計資料表與實體類,有些剛剛入門,很難理清資料之間的關係,使得資料表設計得非常不合理;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>
        /// 住宿舍的話,Buyer.Room.Building+Buyer.Room.Number,如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 };
            //dbContext.Room.Add(r);
            //dbContext.SaveChanges();
            Room r = dbContext.Room.FirstOrDefault();
            #region Student Add
            //List<Student> stuList = new List<Student>();
            //for (int i = 0; i < 5; i++)
            //{
            //    stuList.Add(new Student { Name = "Jack" + i, Number = DateTime.Now.GetHashCode() + "_" + i, RoomId = r.Id });
            //}
            //dbContext.Student.AddRange(stuList);
            //dbContext.SaveChanges();
            #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=0.2 },
            //    new Product { Name="雞腿漢堡", Number="hb001", Price=5, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight=0.3 },
            //    new Product { Name="八寶粥", Number="bbz001", Price=6, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight=0.8 },
            //    new Product { Name="蛋炒飯", Number="dcf001", Price=8, ProductDate=DateTime.Now, ValidDate=DateTime.Now.AddDays(7), Weight=1.2 },

            //};
            //dbContext.Product.AddRange(products);
            #endregion
            #region  模擬購物訂單 
            using (var tran = dbContext.Database.BeginTransaction())
            {
                try
                {
                    //客戶 Jack2
                    Student buyer = dbContext.Student.Where(s => s.Name == "Jack2").FirstOrDefault();
                    //買 八寶粥1份、蛋炒飯2份
                    Product[] items = dbContext.Product.Where(a => a.Name == "八寶粥" || a.Name == "蛋炒飯").ToArray();
                    Order order = new Order
                    {
                        BuyerId = buyer.Id,
                        //校園點餐,不住宿舍的,手輸送貨地點(校園內的)
                        BuyerAddress = buyer.UseRoom ? buyer.Room.Building + buyer.Room.Number : "籃球場第1個垃圾桶旁",
                        Number = DateTime.Now.GetHashCode() + "-" + buyer.Id,
                        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 = dbContext.Order.Add(order);
                    dbContext.SaveChanges();
                    Console.WriteLine(order.Id);
               
                    tran.Commit();
                }
                catch (Exception ex)
                {
                    tran.Rollback();
                    throw ex;
                }
            }
            #endregion
        }

資料庫表效果

Ok,基本上的思路就是這樣。