EF4.1支持了純粹的POCO實體,對編寫Persistence-Ignorant 的程序很有幫助。EF4.1還支持Code First的開發方式,但個人感覺利用Code First在處理較為復雜的關聯的時候還是力不從心,Model First是更加合適的方式。在MVC應用程序中,由於無法長久的保留DbContext,在更新一個實體的時候,通常的場景是這樣的:
ActionResult Edit(Entity entity)
{
//Init a context
// code to update entity
}
在這種情況下,EF自帶的ChangeTracker都起不到任何作用。假如我們有如下模型:
EF會生成如下的兩個實體類:
public partial class Person { public int Id { get; set; } public string Name { get; set; } public virtual Address Address { get; set; } }
public partial class Address { public Address() { this.Person = new HashSet<Person>(); } public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Person> Person { get; set; } }
看起來很完美,但是我們在更新Person的時候會遇到麻煩,例如:
static void Main(string[] args) { var p = new Person { Id = 1, Name = "hello2", Address = new Address { Id = 2 } }; Update(p); } static bool Update(Person p) { using (EFContainer con = new EFContainer()) { con.Entry<Person>(p).State = System.Data.EntityState.Modified; con.SaveChanges(); } return true; }
運行的結果是:
Name屬性被修改了,但是外鍵沒有被修改。
改成這樣:
static bool Update(Person p) { using (EFContainer con = new EFContainer()) { p.Address = con.AddressSet.Find(p.Address.Id); con.Entry<Person>(p).State = System.Data.EntityState.Modified; con.SaveChanges(); } return true; }
結果還是一樣。EF的行為被設計成這樣很令人費解。所幸還有一種方法可以解決這個問題,就是顯式的在Person類中添加Address的外鍵。具體方法是,在EDMX設計器中,給Person類添加一個Scalar Propery,AddressID,在Table Mapping中,將其設置為AddressSet的ID,如下圖:
最后,雙擊表示關聯的線條,彈出一個外鍵約束框,如下設置:
設置完成以后,就可以如下使用:
static void Main(string[] args) { var p = new Person { Id=1, Name = "modified", AddressID=2 }; Update(p); } static bool Update(Person p) { using (EFContainer con = new EFContainer()) { con.Entry<Person>(p).State = System.Data.EntityState.Modified; con.SaveChanges(); } return true; }
一切正常。
純粹從設計的角度來說,在實體類中暴露外鍵——一個在關系數據庫中存在的概念並不是一個很好的設計方法,但是,目前似乎僅能通過這種方法使得EF能夠正確處理外鍵的更新,並且,在某些情況下,暴露外鍵也可以得到一些方便,暫且就這樣吧。
在新增實體的時候,如果不暴露外鍵,也會有種種問題,例如:
static void Main(string[] args) { var p = new Person { Name = "hello5", Address = new Address { Id = 1, Name = "China" } }; Create(p); } static bool Create(Person p) { using (EFContainer con = new EFContainer()) { con.PersonSet.Add(p); con.SaveChanges(); } return true; }
這代碼可以正確運行,但是結果並不是期望的新建一個名字為hello5的Person,其Address為ID=1的Address,事實上,EF會忽略掉Address中的Id=1,新增一個名字為China的Address,再將這個新增的Address的Id和這個Person關聯起來。這樣的行為也很費解,個人認為在顯式指定Address的主鍵的時候就不應該再去試圖新建Address實體,而應該直接關聯,反之,當沒有指定Address的主鍵的時候,應該新建一個。
不管如何,如果有了顯式的外鍵,那么,在需要關聯到已有的對象的時候,就直接使用AddressID來設置,否則就使用new Address來設置。