我今天要介紹怎樣使用Entity Framework Code First的高級配置功能來處理domain driven design 中的另一種重要組成部分:Value Object,中文翻譯過來叫做值對象。
所謂的值對象就是一些沒有生命周期,也沒有業務邏輯上唯一標識符的類。哪些類是Entity,哪些類是Value Object不是固定的,取決於具體的業務邏輯。比如說Customer這個類,如果在CRM系統當中,它是最重要的信息,我們需要跟蹤它的狀態,管理它的生命周期。但是在其他系統中,客戶信息可能只代表一個名字和一些其他的屬性。
值對象應該是不可修改的,因為它只代表一些臨時的屬性集合,並沒有生命周期需要維護。
我們的訂單管理系統中有一個Customer類。在我們以前的示例中,Customer類的Address屬性是一個字符串。現在我們的業務邏輯發生了改變,需要把地址信息分類顯示和保存,把地址信息細分為國家,省,城市,街道及門牌號,還有郵政編碼。在我們的業務中,地址信息僅僅是一些屬性的集合,不需要跟蹤它的生命周期,也不存在業務中的唯一標識符。所以我們把它定義為一個Value Object:
public class Address
{
public string Country { get; set; }
public string Province { get; set; }
public string City { get; set; }
public string StreetAddress { get; set; }
public string ZipCode { get; set; }
}
想要讓Entity Framework Code First默認地識別出值對象,我們的類必須具備三個條件:
1.值對象類不能有主鍵。
2.值對象類只能包含.net基礎類型的屬性。
3.使用值對象的類,只能包含值對象的一個實例,不能使用值對象的集合。
然后我們需要改變我們的Customer類,使用Address值對象來替代以前的字符串。
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; }
}
如果值對象不滿足三個默認的值對象識別條件,我們就需要在我們自定義的DbContext類的OnModelCreating方法中聲明Address是一個值對象。
modelBuilder.ComplexType<Address>();
我們在我們的單元測試中添加一個新的測試方法來使用Address值對象。
[TestMethod]
public void CanAddNewCustomerWithAddress()
{
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" };
newCustomer.Address = customerAddress;
repository.AddNewCustomer(newCustomer);
unitOfWork.CommitChanges();
}
我們使用可插入基礎數據的DropCreateOrderDatabaseWithSeedValueAlways自定義數據庫初始化類,每次執行測試方法之前都重新建立數據庫。
[TestInitialize]
public void InitializeCustomerRepositoryTest()
{
Database.SetInitializer(new DropCreateOrderDatabaseWithSeedValueAlways());
}
測試程序執行之后的Customer表結構如下:
我們可以看到Entity Framework Code First默認會把Address值對象的屬性作為Customers表的列。列的名字默認是值對象類的名字+“_”+值對象屬性的名字。
我們可以改變Entity Framework Code First默認的處理方式。
我們可以定義一個繼承自ComplexTypeConfiguration<Address>的類,在這個類中重載Code First對值對象的默認處理方式。我們可以改變那些列的名字和它們的數據類型和長度。
public class AddressComplexTypeConfiguration:ComplexTypeConfiguration<Address>
{
public AddressComplexTypeConfiguration()
{
Property(a => a.Country).HasColumnName("Country").HasMaxLength(100);
Property(a => a.Province).HasColumnName("Province").HasMaxLength(100);
Property(a => a.City).HasColumnName("City").HasMaxLength(100);
Property(a => a.StreetAddress).HasColumnName("StreetAddress").HasMaxLength(500);
Property(a => a.ZipCode).HasColumnName("ZipCode").HasMaxLength(6);
}
}
我們通過HasColumnName這個方法,可以改變屬性對應的列的名字。Entity Framework Code First默認會使用類中屬性的名字作為列的名字。
重新執行我們的測試方法,Customer表的結構如下:
明天我會介紹與數據表相關的配置。雖然說我寫這個系列的文章主要是為了鞏固我對Entity Framework Code First的學習和使用經驗,但是如果大家覺得我的哪些表達方式不太好,或者太羅嗦,希望大家提意見,我會盡量改正,謝謝
王胖子2012的意見。