在開始寫本次的日記之前,首先需要給大家道個歉。因為最近一直忙於新的項目,所以有一個多月都沒有繼續更新了。
本篇日記我們將詳細探討如何將表現領域的類映射到現有的數據庫。現在的經濟形勢不是太好,很多公司都取消了開發新系統的預算。在這種情況下,通常的做法是把原有的幾個系統修改一下做個集成,先湊合用着得了。如果要對原有的系統做功能提升的話,肯定要重用原來的數據庫結構並做一些小的改進,在這種情況下我們怎樣使用Code First呢?我們可以按照原有系統的業務邏輯和CR(Change Request)中的新業務邏輯建立domain中的類,然后使用Fluent API把這些類映射到原有數據庫的表中,或修改原有系統的數據結構。我相信通過前幾篇的日記,大家已經知道了怎樣去自定義各個方面的數據庫映射了。這種方法雖然給我們機會重新審視我們的業務邏輯,但是原有系統中許多沒有變化的部分也需要按照Code First的方法重頭創建類並映射到原有的數據表,給我們的開發帶來了很多不必要的工作量。為了提高我們的開發效率,減少在使用Code First升級系統時不必要的工作量,微軟發明了Entity Framework Power Tools. 這個工具可以使用反向工程,在原有數據庫的基礎上生成Code First中使用的純粹代表domain的類和數據庫映射配置。博客園上有很多關於這個工具的介紹,大家可以上博客園搜索。這個工具雖然很好,但是他只能幫你減少你的工作量,不可能完全替代你的工作。比如說原來的數據庫中有一個表,你經過對業務邏輯的分析,發現表中的字段應該屬於兩個類,但是你還不能改變現有的數據庫結構,這種情況需要你自己配置數據庫映射來搞定。或者原來數據庫中的兩個表,在你的新的業務邏輯中屬於同一個類,也給根據特定的業務邏輯進行數據庫映射的配置。所以我今天主要介紹如何解決以上的兩個問題。
1.在不修改數據庫結構的前提下,如何將兩個類映射到同一個數據庫表。
2.在不修改數據庫結構的前提下,如何將一個類映射到兩個數據庫表。
1.在不修改數據庫結構的前提下,如何將兩個類映射到同一個數據庫表。
我們已經介紹過一種將兩個類映射成一個表的方法:ComplexType,今天我們將介紹另外一種方法。
假設我們的原有數據庫中有一個Customer表,結構如下:
我們在對現有系統進行升級改造的時候,使用了Code First。請大家注意,這個表中我用紅色方框標識出來列,從業務邏輯上來說,並不是客戶信息的一部分,而是客戶的銀行賬號信息。按照業務邏輯中的實際情況,我們建立了兩個類:Customer和BankAccount。
Customer類:
public class Customer { public string IDCardNumber { get; set; } public string CustomerName { get; set; } public string Gender { get; set; } public Address Address { get; set; } public string PhoneNumber { get; set; } public BankAccount Account { get; set; } }
BankAccount類:
public class BankAccount { public string AccountNumber { get; set; } public DateTime CreatedDate { get; set; } public string BankName { get; set; } public string AccountName { get; set; } }
如果我們需要讓Code First把這兩個類映射到同一個表,這兩個還必須滿足兩個條件:
1.兩個類必須共享同一個主鍵。
2.兩個類之間的關系必須被映射為表之間的一對一關系。
為了滿足這兩個條件,我們首先需要修改BankAccount類,讓BankAccount類使用與Customer類同樣的主鍵。
public class BankAccount { public string IDCardNumber { get; set; } public string AccountNumber { get; set; } public DateTime CreatedDate { get; set; } public string BankName { get; set; } public string AccountName { get; set; } }
我們還必須在Customer的數據庫配置類中手動地映射這兩個類之間的一對一關系。
HasRequired(c => c.Account).WithRequiredDependent();
滿足了以上的兩個條件之后我們還必須使用ToTable方法手動地把這兩個映射到同一個表。
public class CustomerEntityConfiguration:EntityTypeConfiguration<Customer> { public CustomerEntityConfiguration() { ToTable("Customers"); HasKey(c => c.IDCardNumber).Property(c => c.IDCardNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).HasMaxLength(50); Property(c => c.IDCardNumber).HasMaxLength(20); Property(c => c.CustomerName).IsRequired().HasMaxLength(50); Property(c => c.Gender).IsRequired().HasMaxLength(1); Property(c => c.PhoneNumber).HasMaxLength(20); HasRequired(c => c.Account).WithRequiredDependent(); } }
public class BankAccountValueObjectConfiguration: EntityTypeConfiguration<BankAccount> { public BankAccountValueObjectConfiguration() { ToTable("Customers"); HasKey(ba => ba.IDCardNumber).Property(ba => ba.IDCardNumber).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).HasMaxLength(20); Property(ba => ba.AccountNumber).IsRequired().HasMaxLength(50); Property(ba => ba.AccountName).IsRequired().HasMaxLength(100); Property(ba => ba.BankName).IsRequired().HasMaxLength(100); } }
我們修改一下以前使用的單元測試方法來測試Code First是否把這兩個類映射到同一個表:
[TestMethod] public void CanAddCustomerWithBankAccount() { OrderSystemContext unitOfWork = new OrderSystemContext(); CustomerRepository repository = new CustomerRepository(unitOfWork); Customer newCustomer = new Customer() { IDCardNumber = "120104198106072518", CustomerName = "Alex", Gender = "M", PhoneNumber = "test" }; Address customerAddress = new Address { Country = "China", Province = "Tianjin", City = "Tianjin", StreetAddress = "Crown Plaza", ZipCode = "300308" }; BankAccount account = new BankAccount { IDCardNumber = "120104198106072518", AccountNumber = "2012001001", BankName = "ICBC", AccountName = "Alex", CreatedDate = DateTime.Parse("2012-1-21") }; newCustomer.Address = customerAddress; newCustomer.Account = account; repository.AddNewCustomer(newCustomer); unitOfWork.CommitChanges(); }
執行完測試方法之后,我們打開數據庫看一下Code First映射出的數據庫結構是否與我們原來的數據庫結構一樣。
通過手動地將這兩個類映射成同一個表,使我們的代碼可以再不修改原有數據庫結構的基礎上根據業務邏輯重構我們的代碼。
2.在不修改數據庫結構的前提下,如何將一個類映射到兩個數據庫表
假設我們原有的數據庫中有兩個表,一個是ProductCatalog,另一個是ProductCatalogPhoto。ProductCatalog表存儲某類產品的信息,但是后來加新需求的時候,需要顯示該類產品的照片和對照片的描述。不知道是哪位仁兄不敢動ProductCatalog表,直接加了一個ProductCatalogPhoto表。現在輪到我們升級系統的時候,就會面臨下面這樣的數據庫結構:
但是傻子都能看出來,從ProductCatalog和ProductCatalogPhoto兩個表創建出兩個類肯定是不符合業務邏輯的。我們就需要創建ProductCatalog類,它應該既包括某類產品的信息,也應該包括該類產品的照片和對照片的描述。
public class ProductCatalog { public int ProductCatalogId { get; set; } public string CatalogName { get; set; } public string Manufactory { get; set; } public decimal ListPrice { get; set; } public decimal NetPrice { get; set; } public List<Product> ProductInStock { get; set; } public List<SalesPromotion> SalesPromotionHistory { get; set; } public byte[] Photo { get; set; } public string PhotoDescription { get; set; } ............. }
在不改變原來數據庫結構的情況下,我們就需要通過手動配置將ProductCatalog類映射到ProductCatalog和ProductCatalogPhoto兩個表。
public class ProductCatalogEntityConfiguration:EntityTypeConfiguration<ProductCatalog> { public ProductCatalogEntityConfiguration() { this.Property(c => c.CatalogName).HasMaxLength(200).IsRequired(); this.Property(c => c.Manufactory).HasMaxLength(200); this.Property(c => c.ListPrice).HasPrecision(18, 4); this.Property(c => c.NetPrice).HasPrecision(18, 4); this.HasKey(c => c.ProductCatalogId); Map(c => { c.Properties(p => new {p.CatalogName,p.Manufactory,p.ListPrice,p.NetPrice}); c.ToTable("ProductCatalog"); }); Map(c => { c.Properties(p => new {p.Photo,p.PhotoDescription}); c.ToTable("ProductCatalogPhoto"); }); } }
我們通過Map方法,首先選擇類的屬性,然后將選擇的屬性列表映射到某個數據庫表。
我們在我們的測試程序里新加一個單元測試,測試一下,Entity Framework Code First是不是將這個類實例持久化到兩個表中。
[TestMethod] public void CanAddNewProductCatalogWithPhoto() { OrderSystemContext unitOfWork = new OrderSystemContext(); ProductRepository repository = new ProductRepository(unitOfWork); ProductCatalog catalog = new ProductCatalog() { CatalogName = "DELL Laptop E6400", Manufactory = "DELL", ListPrice = 6000, NetPrice = 5000, Photo = new byte[] { 0 }, PhotoDescription = "E6400" }; repository.AddNewProductCatalog(catalog); unitOfWork.CommitChanges(); }
執行完這個單元測試程序之后,我們可以到數據庫中查詢一下,看看是不是我們想要的結果:
大家可以看到,Entity Framework Code First已經將ProductCatalog 類的實例持久化到ProductCatalog和ProductCatalogPhoto兩個表中。
通過這次的筆記我想大家都知道了怎么使遺留的數據庫與Code First兼容。我們下面的筆記將介紹一下如何在Entity Framework Code First中使用視圖和存儲過程。