一對多關係
項目中最常用到的就是一對多關係了。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);
對同一個實體多個引用的情況
我們來考慮一下下面的情況:
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 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既是外鍵也必須是主鍵