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来设置。