在MVC中更新Model-First Entity Framework POCO实体外键的方法

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都起不到任何作用。假如我们有如下模型:

image

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;
}

 

运行的结果是:

image

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,如下图:

image

最后,双击表示关联的线条,弹出一个外键约束框,如下设置:

image

设置完成以后,就可以如下使用:

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

posted @ 2012-06-11 22:03  yinzixin  阅读(2592)  评论(5编辑  收藏  举报