zoukankan      html  css  js  c++  java
  • EF Code First 学习笔记:关系

    一对多关系

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

    public class Destination
    {
    public int DestinationId { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public string Description { get; set; }
    public byte[] Photo { get; set; }
    public List<Lodging> Lodgings { get; set; }
    }

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    public Destination Destination { get; set; }
    }

    复制代码
    public class Destination
        {
            public int DestinationId { get; set; }
            public string Name { get; set; }
            public string Country { get; set; }
            public string Description { get; set; }
            public byte[] Photo { get; set; }
            public List<Lodging> Lodgings { get; set; }
        }
    
        public class Lodging
        {
            public int LodgingId { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
            public bool IsResort { get; set; }
            public decimal MilesFromNearestAirport { get; set; }
            public Destination Destination { get; set; }
        }
    复制代码

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

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

    public class Destination
    {
    public int DestinationId { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public string Description { get; set; }
    public byte[] Photo { get; set; }
    }

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    public Destination Destination { get; set; }
    }

    复制代码
     public class Destination
        {
            public int DestinationId { get; set; }
            public string Name { get; set; }
            public string Country { get; set; }
            public string Description { get; set; }
            public byte[] Photo { get; set; }
        }
    
        public class Lodging
        {
            public int LodgingId { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
            public bool IsResort { get; set; }
            public decimal MilesFromNearestAirport { get; set; }
            public Destination Destination { get; set; }
        } 
    复制代码

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

    public class Destination
    {
    public int DestinationId { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public string Description { get; set; }
    public byte[] Photo { get; set; }
    public List<Lodging> Lodgings { get; set; }
    }

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    }

    复制代码
    public class Destination
        {
            public int DestinationId { get; set; }
            public string Name { get; set; }
            public string Country { get; set; }
            public string Description { get; set; }
            public byte[] Photo { get; set; }
            public List<Lodging> Lodgings { get; set; }
        }
    
        public class Lodging
        {
            public int LodgingId { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
            public bool IsResort { get; set; }
            public decimal MilesFromNearestAirport { get; set; }
        } 
    复制代码

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

     指定外键

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

    public class Destination
    {
    public int DestinationId { get; set; }
    public string Name { get; set; }
    public string Country { get; set; }
    public string Description { get; set; }
    public byte[] Photo { get; set; }
    public List<Lodging> Lodgings { get; set; }
    }

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    //外键
    public int TargetDestinationId { get; set; }
    public Destination Target { get; set; }
    }

    复制代码
     public class Destination
        {
            public int DestinationId { get; set; }
            public string Name { get; set; }
            public string Country { get; set; }
            public string Description { get; set; }
            public byte[] Photo { get; set; }
            public List<Lodging> Lodgings { get; set; }
        }
    
        public class Lodging
        {
            public int LodgingId { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
            public bool IsResort { get; set; }
            public decimal MilesFromNearestAirport { get; set; }
            //外键 
            public int TargetDestinationId { get; set; }
            public Destination Target { get; set; }
        } 
    复制代码

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

    对于命名不规范的列,Code First会怎做呢?

    比如我们将外键改为:

    public int TarDestinationId { get; set; }

    再重新生成数据库:

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

    使用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没有再生成别的外键。

    用Fluent API指定外键:

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

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

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

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    public Destination Target { get; set; }
    //第一联系人
    public Person PrimaryContact { get; set; }
    //第二联系人
    public Person SecondaryContact { get; set; }
    }

    public class Person
    {
    public int PersonID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<Lodging> PrimaryContactFor { get; set; }
    public List<Lodging> SecondaryContactFor { get; set; }
    }

    复制代码
     public class Lodging
        {
            public int LodgingId { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
            public bool IsResort { get; set; }
            public decimal MilesFromNearestAirport { get; set; } 
            public Destination Target { get; set; }
            //第一联系人
            public Person PrimaryContact { get; set; }
            //第二联系人
            public Person SecondaryContact { get; set; } 
        } 
    
        public class Person
        {
            public int PersonID { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public List<Lodging> PrimaryContactFor { get; set; }
            public List<Lodging> SecondaryContactFor { get; set; } 
        }
    复制代码

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

    看看Code First默认会生成怎样的数据库

    天哪,竟然生成了四个外键。因为有两套类型一样的导航属性与引用属性,Code First无法确定它们之间的对应关系,就单独为每个属性都创建了一个关系。这肯定不是我们所期望的,为了让Code First知道它们之间的对应关系,在这里要用到逆导航属性来解决。

    使用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);

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

    多对多关系

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

    public class Lodging
    {
    public int LodgingId { get; set; }
    public string Name { get; set; }
    public string Owner { get; set; }
    public bool IsResort { get; set; }
    public decimal MilesFromNearestAirport { get; set; }
    public Destination Target { get; set; }
    //第一联系人
    public Person PrimaryContact { get; set; }
    //第二联系人
    public Person SecondaryContact { get; set; }
    }

    public class Person
    {
    public int PersonID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<Lodging> PrimaryContactFor { get; set; }
    public List<Lodging> SecondaryContactFor { get; set; }
    }

    复制代码
      public class Activity
         {
             public int ActivityId { get; set; }
             [Required, MaxLength(50)] 
             public string Name { get; set; } 
             public List<Trip> Trips { get; set; }
         }
    
        public class Trip
        {
            public int TripId{get;set;}
            public DateTime StartDate{get;set;}
            public DateTime EndDate { get; set; }
            public decimal CostUSD { get; set; }
            public byte[] RowVersion { get; set; }
            public List<Activity> Activities { get; set; }
        }
    复制代码

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

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

    指定表名

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

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

    或:

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

    一对一关系

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

    public class Person
    {
    public int PersonId { get; set; }
    public int SocialSecurityNumber { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Timestamp]
    public byte[] RowVersion { get; set; }
    public PersonPhoto Photo { get; set; }
    }

    public class PersonPhoto
    {
    [Key]
    public int PersonId { get; set; }
    public byte[] Photo { get; set; }
    public string Caption { get; set; }
    public Person PhotoOf { get; set; }
    }

    复制代码
     public class Person
        {
            public int PersonId { get; set; }
            public int SocialSecurityNumber { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            [Timestamp]
            public byte[] RowVersion { get; set; }
            public PersonPhoto Photo { get; set; }
        }
    
        public class PersonPhoto
        {
            [Key]
            public int PersonId { get; set; }
            public byte[] Photo { get; set; }
            public string Caption { get; set; }
            public Person PhotoOf { get; set; }
        }
    复制代码

    我们为一个(Person)对应着一张相片(PersonPhoto),但如果根据这样的模型生成数据库为报错:

    无法确定类型“BreakAway.PersonPhoto”与“BreakAway.Person”之间的关联的主体端。必须使用关系 Fluent API 或数据注释显式配置此关联的主体端

    因为Code First无法确认哪个是依赖类,必须使用Fluent API或Data Annotations进行显示配置。

    使用Data Annotations

    public class Person
    {
    public int PersonId { get; set; }
    public int SocialSecurityNumber { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Timestamp]
    public byte[] RowVersion { 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; }
    }

    复制代码
    public class Person
        {
            public int PersonId { get; set; }
            public int SocialSecurityNumber { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            [Timestamp]
            public byte[] RowVersion { 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; }
        }
    复制代码

    使用Fluent API:

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

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

    public class User
    {
    [Key]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public bool IsActive { get; set; }
    public System.DateTime CreateDate { get; set; }
    public virtual Student Student { get; set; }
    public virtual ICollection<Teacher> Teachers { get; set; }
    public virtual UserDetail UserDetail { get; set; }
    }

    public class Teacher
    {
    [Key,ForeignKey("User")]
    public int UserId { get; set; }
    [Key, ForeignKey("Class")]
    public int ClassId { get; set; }

    [Key, ForeignKey("Course")]
    public string CourseId { get; set; }

    [Key, ForeignKey("Role")]
    public int RoleId { get; set; }

    public virtual Class Class { get; set; }
    public virtual Course Course { get; set; }
    public virtual Role Role { get; set; }
    public virtual User User { get; set; }
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

    base.OnModelCreating(modelBuilder);
    //一对一
    modelBuilder.Entity<UserDetail>().HasRequired(p => p.User).WithOptional(p => p.UserDetail);
    modelBuilder.Entity<Student>().HasRequired(p => p.User).WithOptional(p => p.Student);
    //一对多
    modelBuilder.Entity<Teacher>().HasKey(t => new { t.UserId, t.CourseId,t.ClassId,t.RoleId });
    modelBuilder.Entity<Teacher>().HasRequired(p => p.User).WithMany(l => l.Teachers).HasForeignKey(p => p.UserId);
    modelBuilder.Entity<Teacher>().HasRequired(p => p.Class).WithMany(l => l.Teachers).HasForeignKey(p => p.ClassId);
    modelBuilder.Entity<Teacher>().HasRequired(p => p.Course).WithMany(l => l.Teachers).HasForeignKey(p => p.CourseId);
    modelBuilder.Entity<Teacher>().HasRequired(p => p.Role).WithMany(l => l.Teachers).HasForeignKey(p => p.RoleId);
    }

  • 相关阅读:
    mtu
    OC2_使用系统协议
    OC1_协议语句
    Json文件/网址解析
    文件归档
    Plist文件
    NS-Date/NSDateFormatter
    OC10_文件练习
    OC9_文件操作
    OC8_NSData
  • 原文地址:https://www.cnblogs.com/Alex80/p/5496767.html
Copyright © 2011-2022 走看看