本文主要学习是类之间的关联是如何映射到数据库中形成表与表间的关系的。这种关系包括 一对多,多对多,一对一。
多重关系
Code First在处理多重性关系时应用了一系列规则。规则使用导航属性确定多重性关系。即可以是一对导航属性互相指定(双向关系),也可以是单个导航属性(单向关系)。
1、如果你的类中包含一个引用和一个集合导航属性,Code First视为一对多关系;
2、如果你的类中仅在单边包含导航属性(即要么是集合要么是引用,只有一种),Code First也将其视为一对多关系;
3、如果你的类包含两个集合属性,Code First默认会使用多对多关系;
4、如果你的类包含两个引用属性,Code First会视为一对一关系;在一对一关系中,你需要提供附加信息以使Code First获知何为主何为辅。如果没有在类中定义外键属性,Code First将设定关系为可选。
大多数多重关系配置都需要使用Fluent API。但可以使用Data Annotations来指定一些关系是必须的。只需要简单地将 [Required] 标记放在你需要定义为必须项的引用属性上。比如:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } public List<Book> MyBooks { get; set; } } public class Book { public int BookId { get; set; }
//外键 //public int AuthorId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } [Required] public Author Author { get; set; } }
但是 Fluent API 则不同,为了达到相同的目的,必须先确定关系。有时在一端就足够,但更多的需要对全部关系进行描述。
为了确定关系,你必须指明导航属性。不管从哪端开始,都要使用这样的代码模板:
Entity.Has[Multiplicity](Property).With[Multiplicity](Property)
多重性关系可以是 Optional(一个属性可拥有一个单个实例或没有),Required(一个属性必须拥有一个单个实例),Many很多的(一个属性可以拥有一个集合或一个单个实例)。
Has方法包括如下几个:
• HasOptional
• HasRequired
• HasMany
在多数情况还需要在Has方法后面跟随如下With方法之一:
• WithOptional
• WithRequired
• WithMany
用FluntAPI来实现上面的关系代码如下:
modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks)
注意:这里只是指定了两个类间的关系,并没有指定外键,如果打开数据库会发现Book表已经生成了外键,其实这是由于,我们指定了关系后,codefirst根据默认配置自动给生成的
下面来总结一下以上这三种关系是如何在开发中使用的
一对多关系:
通过前面的学习我们已经知道了一对多的关系,如第一篇的笔记中,Author类有个集合导航属性List<Book>, 而Book 类中还定义了一个对Author的属性引用,因此 Code First可以确定 Book 与 Author 具有依赖关系,这时候EF会自动识别关系为我们创建外键关联。
外键:
可以在类中,增加一个符合命名规范的字段,充当外键,Code First会将的该属性设置为外键,不再自动创建一个外键。
默认配置:
命名规范:[目标类型的键名] 或 [目标类型名称]+[目标类型键名称] 或 [引用属性名称]+[目标类型键名称] 的形式,如上面代码中,在这里目标类型就是Author,相对应的命名就是:AuthorId,AuthorAuthorId,TargetAuthorId
public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //外键 public int TargetAuthorId { get; set; } public Author Target { get; set; } }
我们也可以使用不符合规范的命名但需要进行手动配置:
DataAnnotation 方式:
//外键(参数为属性名称) [ForeignKey("Author")] public int FkAuthorId { get; set; } public Author Author { get; set; }
或者
public int FkAuthorId { get; set; } //外键 [ForeignKey("FkAuthorId")] public Author Author { get; set; }
注意:[ForeignKey] 的位置不同,后面所带的参数是不同的
FluntAPI 方式:
modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks).HasForeignKey(b => b.AuthorFk);
使用逆导航属性:
书中还提到了一个逆向属性,这个属性主要是应用在一个实体被多次引用的情况下。比如Book修改一下,引用两个Author类,分别为 主要作者,参与作者,Author类中也进行修改,变成两个集合导航属性 主要著作和参与著作,代码如下:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } //主要著作 public List<Book> PrimaryBooks { get; set; } //参与著作 public List<Book> SecondaryBooks { get; set; } } public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //主要作者 public Author Primary { get; set; } //参与作者 public Author Secondary { get; set; } }
这时候,我们运行代码,会发现生成的Book 表如下:
竟然生成了4个外键,这就是因为有两套类型一样的导航属性与引用属性,Code First无法确定它们之间的对应关系,就单独为每个属性都创建了一个关系。这种情况我就可以通过逆向属性来解决。
DataAnnotation 方式:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } //主要著作 public List<Book> PrimaryBooks { get; set; } //参与著作 public List<Book> SecondaryBooks { get; set; } } public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //主要作者 [InverseProperty("PrimaryBooks")] public Author Primary { get; set; } //参与作者 [InverseProperty("SecondaryBooks")] public Author Secondary { get; set; } }
FluntAPI 方式:
modelBuilder.Entity<Book>().HasOptional(b => b.Primary).WithMany(a => a.PrimaryBooks);
modelBuilder.Entity<Book>().HasOptional(b => b.Secondary).WithMany(a=>a.SecondaryBooks);
生成外键如下图:
注意:虽然这种结果是我们想要的,但是是codefirst 自动给生成的,如何自定义这个外键名称,未找到方法。
级联删除:
级联删除允许主记录被删除时相关联的依赖性数据也被删除,比如上面的代码,当我们删除某一个Author的时候,与之相关联的Book中的记录也会被删除。
默认规则约定,Code First会对Required的关系设置级联删除。当一个级联删除定义后,Code First会在数据库中为其创建级联删除。
级联删除的功能是否启用(默认启用),只能通过FluntAPI 方式设置:
//关闭级联删除 modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks).HasForeignKey(b => b.AuthorFk).WillCascadeOnDelete(false);
多对多关系:
EF框架也支持多对多关系,如果两个类分别包含对方的集合导航属性,则认为他们是多对多关系。修改上面的代码,在增加一个Reader类和Book类形成多对多的关系:
public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } public int AuthorFk { get; set; } public Author Author { get; set; } public List<Reader> Readers { get; set; } } public class Reader { public int ReaderId { get; set; } public string ReaderName { get; set; } public List<Book> Books { get; set; } }
来看一下采用默认方式,生成的数据库,我们会发现 自动增加了一个多对多的中间表 ReaderBooks,表中键默认命名为:"[目标类型名称]_[目标类型键名称]"
我们可以通过FluntAPI 对 多对多的关系进行配置:
modelBuilder.Entity<Book>().HasMany(b => b.Readers).WithMany(r => r.Books).Map(m => { m.ToTable("t_ReaderToBook"); m.MapLeftKey("BookId"); m.MapRightKey("ReaderId"); });
或者
modelBuilder.Entity<Reader>().HasMany(r => r.Books).WithMany(b => b.Readers).Map(m => { m.ToTable("t_ReaderToBook"); m.MapLeftKey("ReaderId"); m.MapRightKey("BookId"); });
再次查看数据库:
一对一关系:
有一种关系Code First必须进行配置后才能工作,这种关系就是一对一关系。当你在模型中定义一对一关系,需要在每个类中都要使用引用导航。来看一下书中的例子:
public class Person { public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; }public PersonPhoto Photo { get; set; } } public class PersonPhoto { public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
运行代码,但程序会发生错误:因为Code First无法确认哪个是依赖类,必须使用Fluent API或Data Annotations进行显示配置。
DataAnnotation方式:
[Table("t_Persons")] public class Person { public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public PersonPhoto Photo { get; set; } } public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
FluntAPI 方式:
modelBuilder.Entity<PersonPhoto>().HasKey(p => p.PersonId);
modelBuilder.Entity<PersonPhoto>().HasRequired(p => p.PhotoOf).WithOptional(p => p.Photo);