zoukankan      html  css  js  c++  java
  • EF CodeFirst 关系配置

    自从开始学习asp.net mvc采用code first以来,关系配置就没有搞清楚过!(⊙﹏⊙)b

    笔记之前先感谢以下文章和博主,对他们表示崇拜,由浅入深、举例恰当、拨云见日、茅塞顿开,还有什么词,大家可以自己去拜访后点赞评论!!!参见以下链接:

    http://www.cnblogs.com/Gyoung/archive/2013/01/22/2869782.html

    http://www.cnblogs.com/oppoic/p/ef_one-to-one_one-to-many_many-to-many_cascadedelete.html

    那就开始吧

    01 一对多关系


    项目中最常用到的就是一对多关系了。Code First对一对多关系也有着很好的支持。很多情况下我们都不需要特意的去配置,Code First就能通过一些引用属性、导航属性等检测到模型之间的关系,自动为我们生成外键。观察下面的类:

     1     public class Destination
     2     {
     3         public int DestinationId { get; set; }
     4         public string Name { get; set; }
     5         public string Country { get; set; }
     6         public string Description { get; set; }
     7         public byte[] Photo { get; set; }
     8         public List<Lodging> Lodgings { get; set; }
     9     }
    10 
    11     public class Lodging
    12     {
    13         public int LodgingId { get; set; }
    14         public string Name { get; set; }
    15         public string Owner { get; set; }
    16         public bool IsResort { get; set; }
    17         public decimal MilesFromNearestAirport { get; set; }
    18         public Destination Destination { get; set; }
    19     }

    Code First观察到Lodging类中有一个对Destination的引用属性,同时Destination中又有一个集合导航属性Lodgings,因此推测出Destination与Lodging的关系是一对多关系,所以在生成的数据库中为自动为Lodging表生成外键:

    其实,只要在一个类中存在引用属性,即:

     1     public class Destination
     2     {
     3         public int DestinationId { get; set; }
     4         public string Name { get; set; }
     5         public string Country { get; set; }
     6         public string Description { get; set; }
     7         public byte[] Photo { get; set; }
     8     }
     9 
    10     public class Lodging
    11     {
    12         public int LodgingId { get; set; }
    13         public string Name { get; set; }
    14         public string Owner { get; set; }
    15         public bool IsResort { get; set; }
    16         public decimal MilesFromNearestAirport { get; set; }
    17         public Destination Destination { get; set; }
    18     }

    或一另一个类中存在导航属性:

     1     public class Destination
     2     {
     3         public int DestinationId { get; set; }
     4         public string Name { get; set; }
     5         public string Country { get; set; }
     6         public string Description { get; set; }
     7         public byte[] Photo { get; set; }
     8         public List<Lodging> Lodgings { get; set; }
     9     }
    10 
    11     public class Lodging
    12     {
    13         public int LodgingId { get; set; }
    14         public string Name { get; set; }
    15         public string Owner { get; set; }
    16         public bool IsResort { get; set; }
    17         public decimal MilesFromNearestAirport { get; set; }
    18     }

    Code First都能检测到它们之间一对多的关系,自动生成外键。

    01.1 指定外键

    当然我们也可以自己在类中增加一个外键。默认情况下,如果你的外键命名是规范的话,Code First会将的该属性设置为外键,不再自动创建一个外键,如:

     1     public class Destination
     2     {
     3         public int DestinationId { get; set; }
     4         public string Name { get; set; }
     5         public string Country { get; set; }
     6         public string Description { get; set; }
     7         public byte[] Photo { get; set; }
     8         public List<Lodging> Lodgings { get; set; }
     9     }
    10 
    11     public class Lodging
    12     {
    13         public int LodgingId { get; set; }
    14         public string Name { get; set; }
    15         public string Owner { get; set; }
    16         public bool IsResort { get; set; }
    17         public decimal MilesFromNearestAirport { get; set; }
    18         //指定外键,参见【规范命名】
    19         public int TargetDestinationId { get; set; }
    20         public Destination Target { get; set; }
    21     }

    【规范命名】是指符合:命名为“[目标类型的键名],[目标类型名称]+[目标类型键名称]”,或“[导航属性名称]+[目标类型键名称]”的形式,在这里目标类型就是Destination,相对应的命名就是:DestinationId,DestinationDestinationId,TargetDestinationId

    【关于外键的说明】
    Lodging实体有一个TargetDestinationId外键属性,用来指向相关的Destination实体,它有一个Target导航属性。当一个关联实体有一个导航属性时,实体框架不需要您添加外键属性到您的实体模型,实体框架在需要时会在数据库中自动创建外键。但在实体模型中拥有一个外键会让更新更简单、高效。例如,当您读取一个Lodging实体进行编辑,如果您选择不加载Destination实体,那Destination实体是空的。所以当您更新Lodging实体时,您必须先取得该实体关联的Destination实体。如果在数据模型中包含了外键TargetDestinationId,您就不需要在更新前先取得Destination实体。

    之前很多次见到命名规范,没有浅下心理解,总是云里雾里,以上示例可以帮助你完全理解!

    对于命名不规范的列,Code First会怎做呢?比如我们将外键改为:

    public int TarDestinationId { get; set; }

    再重新生成数据库:

    可以看到Code First没有识别到TarDestinationId是一个外键,于是自己创建了一个外键:Target_DestinationId。这时我们要告诉Code First该属性是一个外键。

    01.2 使用Data Annotations指定外键:

            [ForeignKey("Target")]
            public int TarDestinationId { get; set; }
            public Destination Target { get; set; }

            public int TarDestinationId { get; set; }
            [ForeignKey("TarDestinationId")]
            public Destination Target { get; set; }

    注意ForeignKey位置的不同,其后带的参数也不同。这样,生成的数据库就是我们所期望的了。Code First没有再生成别的外键。

    01.3 用Fluent API指定外键:

    modelBuilder.Entity<Lodging>().HasRequired(p => p.Target).WithMany(l => l.Lodgings).HasForeignKey(p => p.TarDestinationId);

    扩展

    01.4 对同一个实体多个引用的情况

    我们来考虑一下下面的情况:

     1     public class Lodging
     2     {
     3         public int LodgingId { get; set; }
     4         public string Name { get; set; }
     5         public string Owner { get; set; }
     6         public bool IsResort { get; set; }
     7         public decimal MilesFromNearestAirport { get; set; }
     8         public Destination Target { get; set; }
     9         //第一联系人
    10         public Person PrimaryContact { get; set; }
    11         //第二联系人
    12         public Person SecondaryContact { get; set; }
    13     }
    14 
    15     public class Person
    16     {
    17         public int PersonID { get; set; }
    18         public string FirstName { get; set; }
    19         public string LastName { get; set; }
    20         public List<Lodging> PrimaryContactFor { get; set; }
    21         public List<Lodging> SecondaryContactFor { get; set; }
    22     }

    Lodging(旅店)有两个对Person表的引用,分别是PrimaryContact与SecondaryContact,同时,在Person表中也有对这两个联系人的导航:PrimaryContactFor与SecondaryContactFor。

    使用Data Annotations:

            //第一联系人
            [InverseProperty("PrimaryContactFor")]
            public Person PrimaryContact { get; set; }
            //第二联系人
            [InverseProperty("SecondaryContactFor")]
            public Person SecondaryContact { get; set; } 

    或使用Fluent API:

    modelBuilder.Entity<Lodging>().HasOptional(l => l.PrimaryContact).WithMany(p => p.PrimaryContactFor);
    modelBuilder.Entity<Lodging>().HasOptional(l=>l.SecondaryContact).WithMany(p=>p.SecondaryContactFor);

    再重新生成数据库,结果如图:

    02 多对多关系


    如果有两个类中,各自都是导航属性指向另一个类,Code First会认为这两个类之间是多对多关系,例如:

     1   public class Activity
     2      {
     3          public int ActivityId { get; set; }
     4          [Required, MaxLength(50)] 
     5          public string Name { get; set; } 
     6          public List<Trip> Trips { get; set; }
     7      }
     8 
     9     public class Trip
    10     {
    11         public int TripId{get;set;}
    12         public DateTime StartDate{get;set;}
    13         public DateTime EndDate { get; set; }
    14         public decimal CostUSD { get; set; }
    15         public byte[] RowVersion { get; set; }
    16         public List<Activity> Activities { get; set; }
    17     }

    一个Trip类可以有一些Activites日程,而一个Activity日程又可以计划好几个trips(行程),显然它们之间是多对多的关系。我们看看默认生成的数据库是怎么样的:

    可以看到,Code First生成了一张中间表ActivityTrips,将另外两张表的主键都作为外键关联到了中间表上面。中间表中键的命名默认为"[目标类型名称]_[目标类型键名称]"。

    指定表名

    如果我们想指定中间表的名称和键名称,我们可以用Fluent API来配置。

    1 modelBuilder.Entity<Trip>().HasMany(t => t.Activities).WithMany(a => a.Trips).Map(m =>
    2                 {
    3                     m.ToTable("TripActivities");
    4                     m.MapLeftKey("TripIdentifier");//对应Trip的主键
    5                     m.MapRightKey("ActivityId");
    6                 });

    或:

    1  modelBuilder.Entity<Activity>().HasMany(a => a.Trips).WithMany(t => t.Activities).Map(m =>
    2                 {
    3                     m.ToTable("TripActivities");
    4                     m.MapLeftKey("ActivityId");//对应Activity的主键
    5                     m.MapRightKey("TripIdentifier");
    6                 });

    03 一对一关系


    例03.1

    如果我们要将两个类配置为一对一关系,则两个类中都要配置相应的引用属性,如:

     1     public class Person
     2     {
     3         public int PersonId { get; set; }
     4         public int SocialSecurityNumber { get; set; }
     5         public string FirstName { get; set; }
     6         public string LastName { get; set; }
     7         [Timestamp]
     8         public byte[] RowVersion { get; set; }
     9         public PersonPhoto Photo { get; set; }
    10     }
    11 
    12     public class PersonPhoto
    13     {
    14         [Key]
    15         public int PersonId { get; set; }
    16         public byte[] Photo { get; set; }
    17         public string Caption { get; set; }
    18         public Person PhotoOf { get; set; }
    19     }

    我们为一个(Person)对应着一张相片(PersonPhoto),但如果根据这样的模型生成数据库为报错:
    无法确定类型“BreakAway.PersonPhoto”与“BreakAway.Person”之间的关联的主体端。必须使用关系 Fluent API 或数据注释显式配置此关联的主体端
    因为Code First无法确认哪个是依赖类,必须使用Fluent API或Data Annotations进行显示配置。

    使用Data Annotations:

     1     public class Person
     2     {
     3         public int PersonId { get; set; }
     4         public int SocialSecurityNumber { get; set; }
     5         public string FirstName { get; set; }
     6         public string LastName { get; set; }
     7         [Timestamp]
     8         public byte[] RowVersion { get; set; }
     9         public PersonPhoto Photo { get; set; }
    10     }
    11 
    12     public class PersonPhoto
    13     {
    14         [Key, ForeignKey("PhotoOf")]
    15         public int PersonId { get; set; }
    16         public byte[] Photo { get; set; }
    17         public string Caption { get; set; }
    18         public Person PhotoOf { get; set; }
    19     }

    使用Fluent API:

    modelBuilder.Entity<PersonPhoto>().HasRequired(p => p.PhotoOf).WithOptional(p => p.Photo);

    注意:PersonPhoto表中的PersonId既是外键也必须是主键

    例03.2

     1 public class Instructor
     2 {
     3    public int ID { get; set; }
     4 
     5    [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
     6    public string LastName { get; set; }
     7 
     8    [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
     9    public string FirstMidName { get; set; }
    10 
    11    [DataType(DataType.Date),Display(Name = "Hire Date")]
    12    public DateTime HireDate { get; set; }
    13 
    14    [Display(Name = "Full Name")]
    15    public string FullName
    16    {
    17       get { return LastName + ", " + FirstMidName; }
    18    }
    19 
    20    public virtual ICollection<Course> Courses { get; set; }
    21    public virtual OfficeAssignment OfficeAssignment { get; set; }
    22 }
     1 using System.ComponentModel.DataAnnotations;
     2 using System.ComponentModel.DataAnnotations.Schema;
     3 
     4 namespace ContosoUniversity.Models
     5 {
     6     public class OfficeAssignment
     7     {
     8         [Key]
     9         [ForeignKey("Instructor")]
    10         public int InstructorID { get; set; }
    11 
    12         [StringLength(50)]
    13         [Display(Name = "办公室地址")]
    14         public string Location { get; set; }
    15 
    16         public virtual Instructor Instructor { get; set; }
    17     }
    18 }

    【Key特性】
    Instructor和OfficeAssignment实体之间有一个对零或一对一的关系。办公室只和讲师之间存在关系,因此其主键也是其Instructor实体的外键。但是实体框架不会自动将InstructorID识别为实体的主键,因为该命名不遵循实体框架约定。因此,我们使用Key特性来标记该属性为实体的主键:

    1         [Key]
    2         [ForeignKey("Instructor")]
    3         public int InstructorID { get; set; }

    如果实体没有它自己的主键,但您想将属性名命名为类名-ID或ID以外的不同的名称,您同样可以使用Key特性。默认情况下实体框架将键视为非数据库生成的,因为该列用来标识关系。

    【ForeignKey特性】
    当两个实体之间存在有一对零或一对一关系时,实体框架无法自动辨认出那一端的关系是主体,那一端是依赖。一对一关系在每个类中使用导航属性来引用其他类。ForeignKey特性可以应用于要建立关系的依赖类。如果您省略ForeignKey特性,当您尝试创建迁移时系统会出现一个无法确定实体间关系的错误。

    【Instructor导航属性】
    Instructor实体有一个可为空的OfficeAssignment导航属性(因为可能有讲师没有分配办公室),并且OfficeAssignment实体有一个不可为空的Instuctor导航属性(因为一个办公室不可能在没有讲师的情况下分配出去--InstructorID是不可为空的)。当Instructor实体具有OfficeAssignment实体关联的时候,每个实体在导航属性中都有另一个的引用。
    您可以把一个Required特性添加给Instructor导航属性来指明必须有相关的讲师,但您不需要这样做。因为InstructorId外键(同样也是表的主键)是不可为null的。

    04 自关联的关系


    很常见的一种情况,自关联的表。就是存在自己关联自己的表结构,比如:树。如下所示:

    使用EF模型生成向导,最终生成的模型如下图所示:

    对于自关联的表,EF会生成一个实体,并同时有导航到父和子实体的导航属性。(导航属性的名称默认可不是Child或者Parent,我们可以在导航属性的属性窗口,根据关系类型,修改成一个容易明白名称,如上图所示)。

    这样,我们就可以很容易对种存在树形结构的表进行操作了,如下所示:

    1 using (var db = new ExampleEFEntities())
    2   {
    3     var root = new Tree() { TreeId = 1, TreeName = "董事会" };
    4     var child1 = new Tree() { ParentTree = root, TreeName = "总经办", TreeId = 2 };
    5     var child2 = new Tree() { ParentTree = child1, TreeName = "各部门", TreeId = 3 };
    6     db.Tree.AddObject(root);
    7     db.SaveChanges();
    8   }

    在代码中,我们只需要给对应的ParentTree或ChildTree赋值,那么他们直接的父、子关系就可以正确的保存到数据库中了。相比以前写存储过程、递归来操作这种树型结构,确实方便了很多。

    就是这么多!需要反复的理解和消化,脑袋笨+记忆力差=不适合玩Code

  • 相关阅读:
    053592
    053591
    053590
    053589
    053588
    053676
    C# WPF Border控件总结
    Android Studio 添加jar或aar依赖的两种方式
    javascript Date与string之间的转换
    C#:使用dsoframer.ocx控件实现内嵌office效果(详解)
  • 原文地址:https://www.cnblogs.com/duanyong/p/4993850.html
Copyright © 2011-2022 走看看