1. 程式人生 > >EF Code First 導航屬性 與外來鍵

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

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

規範命名是指符合:命名為“[目標型別的鍵名],[目標型別名稱]+[目標型別鍵名稱]”,或“[導航屬性名稱]+[目標型別鍵名稱]”的形式,在這裡目標型別就是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; }      }

Lodging(旅店)有兩個對Person表的引用,分別是PrimaryContact與SecondaryContact,同時,在Person表中也有對這兩個聯絡人的導航:PrimaryContactFor與SecondaryContactFor。

看看Code First預設會生成怎樣的資料庫

天哪,竟然生成了四個外來鍵。因為有兩套型別一樣的導航屬性與引用屬性,Code First無法確定它們之間的對應關係,就單獨為每個屬性都建立了一個關係。這肯定不是我們所期望的,為了讓Code First知道它們之間的對應關係,在這裡要用到逆導航屬性來解決。

使用Data Annotations:

       //第一聯絡人
        [InverseProperty("PrimaryContact")] 
        public List<Lodging> PrimaryContactFor { get; set; }
        //第二聯絡人
        [InverseProperty("SecondaryContact")] 
        public List<Lodging> SecondaryContactFor { 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 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; }
    }

我們為一個(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; }
    }

使用Fluent API:

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

 注意:PersonPhoto表中的PersonId既是外來鍵也必須是主鍵