【譯著】Code First :使用Entity. Framework編程(5)


第五章

對數據庫映射使用默認規則與配置

 

    到目前為止我們已經領略了Code First的默認規則與配置對屬性、類間關系的影響。在這兩個領域內,Code First不僅影響模型也影響數據庫。在這一章,你將讓默認規則與配置的目光聚焦在類映射到數據庫上而不影響概念模型。

     我們從簡單的映射開始,設法指定數據庫的表名,構架與屬性。在此你將掌握如何讓多個類映射到一個通用表中,或將單個類映射到多個表中。最后,帶您漫步各種繼承架構的配置。

將類名映射到數據庫表名和構架名

EF框架使用模型的類名的復數形式來生成數據庫表名—Destination變成Destinations,Person變成People等。默認的類命名規則可能與您設定的命名規則不一致,例如需要指定非復數形式的表名(例如PersonPhoto類不要映射到PersonPhotoes表上去),或者你想映射到一些現有的表,其表名恰好與Code First的規則不同。

可以使用Data Annotations 來確保Code First將你的類映射到正確的表名上。使用Table標記,就可改變數據庫構架的表名。

數據庫表命名對其他映射至關重要,正如你將在本章看到的,包括實體分割,繼承層次結構,甚至多對多映射。

默認規則里Code First復數化類名,然后使用復數化后的結果作為類映射的表名。另外,默認情況下所有的表都被安置到dbo構架里。

使用Data Annotations 配置表和構架名

     Table標記允許你改變類映射的表名。在上一章,PersonPhoto被不恰當地復數化了,表名為PersonPhotoes。盡管這樣仍可以正常工作,但直接使用PersonPhoto也是可行的:

[Table("PersonPhotos")]
public class PersonPhoto

另一個例子是原始的BrakAway數據庫,在Programming Entity Framework的第一,二版都使用過.包含相關目的地信息的表名為Locations。如果你映射到此表,就必須指定表名:

[Table("Locations")]
public class Destination

Table標記還有一個參數可以指定構架名.這里是一個與表名共同指定的例子:

[Table("Locations", Schema="baga")]
public class Destination

記住VB的語法有些不同:

<Table("Locations", Schema:="baga")>
Public Class Destination

圖5-1顯示了baga構架下的Locations表:

image

第二個參數不是必須的,使用Table("Locations") 標記是完全合法的.但是,如果你只想指定構架不想指定表名,仍然需要提供第一個參數,這個參數不能是empty,空格或null,因此你必須提供表名.

使用Fluent API配置表和構架名

Fluent API有一個ToTable方法用於指定表名稱構架名.需要兩個參數,第一個是表名,第二個是構架名.如同Data Annotations 一樣,你可以提供表名參數而不提供構架名,但不能在指定構架時不包含表名:


映射屬性名到數據庫列

      不僅可以重新映射表名,也可修改預設的數據庫列名。根據默認規則,Code First使用屬性名作為映射的列名,但這並不總是符合需要的。例如,在原有的BreakAway數據庫中,不僅包含有目的地信息的表名區別於默認設置,而且主鍵字段也被稱為LocationID,而不是DestinationId.並且字段中還包含了一個稱為LocationName的字段。

使用Data Annotations 修改默認的列名


你可能會回想起使用Column標記去修改列的數據類型。同樣的標記也可以用來對列名進行修改,如代碼5-1

Example 5-1. Specifying column names for properties using Data Annotations

[Column("LocationID")]
public int DestinationId { get; set; }
[Required, Column("LocationName")]
public string Name { get; set; }
使用Fluent API修改默認列名

HasColumnName是一個用於指定屬性在數據庫中列名的Fluent方法,如代碼5-2所示,效果與圖5-1所示的相同。bgga.Locations表配置結果合適地映射了Destination類。注意HasColumnName可以附加到現有的屬性配置上。

Example 5-2. Specifying column names for properties using the Fluent API

public class DestinationConfiguration :
 EntityTypeConfiguration<Destination>
{
  public DestinationConfiguration()
  {
    Property(d => d.Nam
      .IsRequired().HasColumnName("LocationName");
    Property(d => d.DestinationId).HasColumnName("LocationID");
對復雜類型的列名施加影響

第3章,我們已經創建了一個來自於Address類的復雜類型,將在Person類中添加了一個Address屬性。你可能會想起Code First對Person.Address映射列的命名方式為Address_StreetAddress 或 Address_State等。

你可以使用前面對Destination類類似的配置方法來調整Adderss復雜類型在數據庫中的列名。

Example 5-3. Configuring column names to be used in the table of any class that hosts the complex type

[ComplexType]
public class Address
{
  public int AddressId { get; set; }
  [MaxLength(150)]
  [Column("StreetAddress")]
  public string StreetAddress { get; set; }
  [Column("City")]
  public string City { get; set; }
  [Column("State")]
  public string State { get; set; }
  [Column("ZipCode")]
  public string ZipCode { get; set; }
}

現在任何在Address復雜類型中的屬性都可以使用這些Data Annotations 標記來指定列名了。

如果你需要流暢地配置列名,你可以在兩種方式內選擇:1)任何以Address作為屬性的類中統一進行命名;2)顯示地在每個“宿主”類中進行單獨配置;

代碼5-4和5-5顯示Fluent API配置應用於Address復雜類型上的情況。這與使用Data Annotations 進行配置時,效果相同,所有使用Address的類都會使用這些列名。

Example 5-4. Configuring a complex type column name from the modelBuilder

modelBuilder.ComplexType<Address>()
  .Property(p => p.StreetAddress).HasColumnName("StreetAddress");

Example 5-5. Configuring the complex type column name from a configuration class

public class AddressConfiguration :
  ComplexTypeConfiguration<Address>
{
  public AddressConfiguration()
  {
    Property(a => a.StreetAddress).HasColumnName("StreetAddress");
  }
}

這種配置的效果使得Person表中的Address_StreetAddress簡化為SteetAddress.

代碼5-6顯示了一個配置Person實體的導航屬性來控制StreetAddress列名的例子。首先,配置是分別在OnModelCreating方法和configuration類中進行暴露。在Person類中的配置將會只影響People表的列名。如果在另一個類中也有Address屬性,它的表不會使用這種列名。

Example 5-6. Configuring the StreetAddress column name to be used when Address is a property of Person

modelBuilder.Entity<Person>()
             .Property(p => p.Address.StreetAddress)
             .HasColumnName("StreetAddress");
public class PersonConfiguration : EntityTypeConfiguration<Person>
{
  public PersonConfiguration()
  {
    Property(p => p.Address.StreetAddress)
      .HasColumnName("StreetAddress");
  }
}

 

讓多個實體映射到同一個表:aka表切分

      通常一個數據庫表中雖然有很多列,但在很多場景只需要使用其中的一部分,其他的只是一些附加的數據。當我們映射一個實體到這樣的表以后,你會發現要浪費資源來處理一些無用的數據。表切分技術可以解決這個問題,這種技術允許在一個單獨表中訪問多個實體。下面就介紹如何使用Code First來配置表切分功能。

     

       在映射到一個現存的數據庫時你可能更想使用表切分技術,因為這種情況下你會發現場景與上面描述的很相似,在這種情況下你甚至都想讓Code First來會創建一個新的數據庫。

      我們更想person的photo和caption直接儲存在People表中而不是存在單獨的PersonPhotos表中。由於我們訪問person的姓名與個人信息頻率要遠高於photo,讓photo放在一個單獨的類中會工作得很好。代碼5-7回顧了PersonPhoto的類,也回顧了Person類中的Photo屬性。我們來配置Photo屬性為一個image列,正如我們在第2章對Destination.Photo屬性所做的一樣。

Example 5-7. The PersonPhoto class with Data Annotations

[Table("PersonPhotos")]
public class PersonPhoto
{
  [Key , ForeignKey("PhotoOf")]
  public int PersonId { get; set; }
  [Column(TypeName="image")]
  public byte[] Photo { get; set; }
  public string Caption { get; set; }
  public Person PhotoOf { get; set; }
}

使用默認規則,Code First會將PersonPhoto映射到它自已的表上,表名依照我們的配置命名為PersonPhotos.

為了將多個實體映射到一個通用的表中,實體必須遵循如下規則:

  • 實體必須是一對一關系
  • 實體必須共享一個通用鍵

Person和PersonPhoto類符合這些條件;(原文中PersonTable應有誤,在此更改為PersonPhoto,譯者注)

使用Data Annotations 映射到通用表

      設置這種映射需要使用Data Annotations 的Table標記。因為我們知道Person實體映射到People表,你可以配置PersonPhoto類也映射到這個表中。但是你需要對所有相關類指定表名,否則,EF框架就會使用另一個默認規則來避免表名重復。你可以在圖5-2看到這種錯誤的結果。因為Table標記的配置,PersonPhoto表被命名為People,因此當Code First嘗試自動為Person類命名表名時,發現People已經使用過了,只好命名為People1.

image

因此我們必須對兩個類作同一命名:

[Table("People")]
public class Person
[Table("People")]
public class PersonPhoto


隨着這種對模型的調整,Code First會再次重建數據庫。People表現在有了Photo和Caption字段(見圖5-3),不再有PersonPhotos表。

如果你回到InsertPerson和UpdatePerson方法,你會發現不需要任何修改方法仍然能夠工作。類仍然是單獨的。EF框架可以識別表的映射,對數據庫執行正確的命令。

更有意思的是當你查詢一個實體時不用再浪費資源從其他實體中抽取數據了。

再次執行對Person類的查詢,如context.People.ToList(),EF框架項目只將這些列映射到Person類而沒有映射到PersonPhoto的任何字段。(代碼5-8)。

image

Example 5-8. SQL Query retrieving subset of table columns

SELECT
[Extent1].[PersonId] AS [PersonId],
[Extent1].[SocialSecurityNumber] AS [SocialSecurityNumber],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName],
[Extent1].[Info_Weight_Reading] AS [Info_Weight_Reading],
[Extent1].[Info_Weight_Units] AS [Info_Weight_Units],
[Extent1].[Info_Height_Reading] AS [Info_Height_Reading],
[Extent1].[Info_Height_Units] AS [Info_Height_Units],
[Extent1].[Info_DietryRestrictions] AS [Info_DietryRestrictions],
[Extent1].[StreetAddress] AS [StreetAddress],
[Extent1].[City] AS [City],
[Extent1].[State] AS [State],
[Extent1].[ZipCode] AS [ZipCode]
FROM [dbo].[People] AS [Extent1]

感謝在Person和PersonPhoto類之間建立的關系,你可以輕松地加載photo數據,例如使用context.People.Include(“Photo”)或者顯示地在需要時加載,或者使用延遲加載。這些都是不能使用標量屬性來實現的功能。


 

延遲加載分割表數據

 

    雖然DbContext默認情況下啟用 延遲加載,但我們目前尚未討論如何使你的代碼類利用延遲加載。事實上,由於在EF4引入了POCO的支持,任何EF框架中的簡單類都可具有這項功能。任何使用virturl關鍵字(在Visual Basic為Overridable)的導航屬性都會在第一次數據庫檢索時自動應用延遲加載。
例如,你可以改變Photo的屬性,以便它可以延遲加載:

[Required]
public virtual PersonPhoto Photo { get; set; }

如下的代碼示例顯示了Photo的延遲加載。一個查詢返回了數據庫中所有的Person數據,但相關的Photo數據沒有在查詢中使用。然后代碼會對某個Person執行一些任務。在最后一行,代碼顯示Person的圖片Caption被導航到了Person.Photo.Caption.由於此時Photo在內存中還沒有加載,此調用將觸發實體框架運行后台的查詢,並從數據庫中檢索數據。就代碼而言,Photo就在那里。如果Photo屬性不是虛擬的,或延遲加載被上下文顯示地禁用,最后一行代碼將拋出一個異常,因為照片的屬性將會為null:

var people = context.People.ToList();
var firstPerson = people[0];
SomeCustomMethodToDisplay(firstPerson.Photo.Caption);
使用Fluent API切分表

正如Data Annotations 一樣,你只需要為類指定表名即可,但是必須要為所有包含的類指定同樣的表名。如下代碼顯示了使用Fluent API配置切分表的方法,直接使用了OnModelCreating方法中的modelBuilder實例,你也可以使用相應的EntityTypeConfiguration類來配置:

modelBuilder.Entity<Person>().ToTable("People");
modelBuilder.Entity<PersonPhoto>().ToTable("People");
映射一個單獨的實體到多個表

       現在,我們將完全翻轉最后一個場景。我們不需要經常看到照片,因為我們只需要看名字就可以。另一個比較常見的情況是當我們檢索目的地時希望能夠隨時看到目的地的照片。如果你映射到一個現有的數據庫,有可能照片已經被存儲在一個數據庫單獨的表中,原因可能是為了規范化,性能,或其他什么原因。但您的域模型表示所有的數據都是在一個單獨的類中。這就需要將單個的Destination類分配到兩個數據庫的表中,以獲取所有的詳細信息。這種映射稱為實體分割。

這個映射的關鍵是Code First配置一系列屬性映射到一個特定的表的能力。實現這個功能不能使用Data Annotations ,因為Data Annotations 沒有子屬性的概念。

Fluent API有一個Map方法可以使用你分配一個屬性的列表到一個表名。我們將使用這個方法映射部分Destination的屬性到Locations表和其他一些屬性映射到名為LocationPhotos的表。務必不要跳過任何屬性!
關於在Destinaion類中進行了一系列的配置,代碼5-9展示了在DestinationConfiguration類中的配置方法:

Example5-9. DestinationConfiguration with Entity Splitting mapping at the end

public class DestinationConfiguration :
  EntityTypeConfiguration<Destination>
{
  public DestinationConfiguration()
  {
    Property(d => d.Name)
     .IsRequired().HasColumnName("LocationName");
    Property(d => d.DestinationId).HasColumnName("LocationID");
    Property(d => d.Description).HasMaxLength(500);
    Property(d => d.Photo).HasColumnType("image");
    // ToTable("Locations", "baga");
    Map(m =>
        {
          m.Properties(d => new
             {d.Name, d.Country, d.Description });
          m.ToTable("Locations");
        });
    Map(m =>
        {
          m.Properties(d => new { d.Photo });
          m.ToTable("LocationPhotos");
        });
  }
}

Map配置中的Lambda表達式是多行表達式語句,你看到里面的Lambda表達式有分號。多行的Lambda語句VB也是支持的,相同的配置類在VB中如下所示:

Example 5-10. DestinationConfiguration using Visual Basic syntax

Public Class DestinationConfiguration
  Inherits EntityTypeConfiguration(Of Destination)
  Public Sub New()
Me.Property(Function(d) d.Name)
     .IsRequired().HasColumnName("LocationName")
    Me.Property(Function(d) d.DestinationId)
     .HasColumnName("LocationID")
    Me.Property(Function(d) d.Description).HasMaxLength(500)
    Me.Property(Function(d) d.Photo).HasColumnType("image")
    Me.Ignore(Function(d) d.TodayForecast)
    ' Me.ToTable("Locations") REM replaced by table mapping below
    Me.Map(Sub(m)
             m.Properties(Function(d) New With
              {Key d.Name, Key d.Country, Key d.Description})
             m.ToTable("Locations")
           End Sub)
    Map(Sub(m)
          m.Properties(Function(d) New With {Key d.Photo})
          m.ToTable("LocationPhotos")
        End Sub)
  End Sub
End Class

最后,我們可以看到在圖5-4數據庫的效果。

請注意即使我們只有Photo屬性映射到LocationPhotos表,Code First也會共享該表的主鍵和外鍵。這也創建了Location和LocationPhotos之間的PK / FK約束。有趣的是,沒有為LocationPhotos表中定義級聯刪除。但是,EF框架知道,如果你刪除一個目的地,它必須建立一個跨越兩個表的刪除命令。讓我們來看看EF框架在對Destination對象的各種CRUD操作生成的SQL。
代碼5-12顯示了我們在第2章中插入一個Destination對象方法,然后調用了SaveChanges方法保存數據到數據庫。

image

Example 5-12. Insert a single object that maps to two database tables

private static void InsertDestination()
{
  var destination = new Destination
    {
      Country = "Indonesia",
      Description = "EcoTourism at its best in exquisite Bali",
      Name = "Bali"
    };
  using (var context = new BreakAwayContext())
  {
    context.Destinations.Add(destination);
    context.SaveChanges();
  }
}
exec sp_executesql
N'insert [dbo].[Locations]([LocationName], [Country], [Description])
values (@0, @1, @2)
select [LocationID]
from [dbo].[Locations]
where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',
N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 nvarchar(500)',
@0=N'Bali',@1=N'Indonesia',@2=N'EcoTourism at its best in exquisite Bali'
exec sp_executesql
N'insert [dbo].[LocationPhotos]([LocationID], [Photo])
  values (@0, null)',
N'@0 int',@0=1

第一個SQL語句向Locations表中插入數據,此語句中的代碼返回了新生成的LocationID的值。第二個SQL語句,插入一個新行到LocationPhotos,包含了從第一個命令返回LocationID。由於該方法沒有提供任何照片信息,照片字段的值插入到該表是空的。

例5-13顯示了代碼來查詢,更新和刪除Destination數據。你可以看到通過SQL,EF框架如何在響應SaveChanges方法時處理多個表的關系的。

Example 5-13. Query, update and delete a Destination
using (var context = new BreakAwayContext())
      {
        var destinations = context.Destinations.ToList();
        var destination = destinations[0];
        destination.Description += "Trust us, you'll love it!";
        context.SaveChanges();
        context.Destinations.Remove(destination);
        context.SaveChanges();
      }
—-RESPONSE TO QUERY
SELECT
[Extent1].[LocationID] AS [LocationID],
[Extent2].[LocationName] AS [LocationName],
[Extent2].[Country] AS [Country],
[Extent2].[Description] AS [Description],
[Extent1].[Photo] AS [Photo]
FROM  [dbo].[LocationPhotos] AS [Extent1]
INNER JOIN [dbo].[Locations] AS [Extent2] ON [Extent1].[LocationID] = [Extent2].
[LocationID]
—RESPONSE TO UPDATE
exec sp_executesql
N'update [dbo].[Locations]
  set [Description] = @0
  where ([LocationID] = @1)',
N'@0 nvarchar(500),@1 int',
@0='Trust us, you''ll love it!',@1=1
--RESPONSE TO DELETE
exec sp_executesql N'delete [dbo].[LocationPhotos]
where ([LocationID] = @0)',N'@0 int',@0=1
exec sp_executesql N'delete [dbo].[Locations]
where ([LocationID] = @0)',N'@0 int',@0=1


 

現在,我們來看看實體分割的結果,把所有的Destination數據再存到一個表中。如果您已經在Visual Studio中,刪除了我們剛才添加的實體分割配置,恢復單個ToTable調用映射,從Destination映射到Locations表:

ToTable("Locations", "baga");
控制到映射到數據庫中數據類型

每次你添加一個類模型,你也得添加到一個DbSet到BreakAwayContext 。 DbSet提供兩個功能。首先,它返回一個特定類型的可查詢數據集。其次可以讓DbModelBuilder知道,每個數據集中引用的類型應包括在模型中。

但是確保一個類型成為你的模型的一部分,這不是唯一的途徑。包括一個類型到模型中有三種方式:
1。在上下文暴露類型的DbSet。
2。另一個已經映射到類型中被引用(即,該類型可以通過另一種類型與模型接觸)。
3。從任何Fluent API調用的DbModelBuilder引用一個類型。
你已經見過第一種方式。下面介紹其他兩種方式。
我們將添加一個新的類到模型中:Reservation。


Example 5-14. The new Reservation class

namespace Model
{
  public class Reservation
  {
    public int ReservationId { get; set; }
    public DateTime DateTimeMade { get; set; }
    public Person Traveler { get; set; }
    public Trip Trip { get; set; }
    public DateTime PaidInFull { get; set; }
  }
}

如果你運行程序,DbModelBuilder並不知道這個類的存在。因為即沒有DbSet<Reservation>也沒有其他兩種形式的配置。因此,模型必須進行更改,數據庫才會重新創建。如果你現在看數據庫,里面不會有Reservation表。現在我們進入Person類,添加一個屬性以便我們可以看到所有由Person設置的Resvervations。

public List<Reservation> Reservations { get; set; }

再次運行應用程序,現在你會看到數據庫中有了一個Reservations表,如圖5-5所示:

image

根據默認規則,由於Person是模型中的類,而Person找到了Reservationod ,Code First就會將Reservation類放進模型中。

現在來看看第三種默認規則:通過提供一個配置將類包括在模型中。

首先我們將Person類中的Reservation屬性注釋掉:

// public List<Reservation> Reservations { get; set; }

為reservation添加Fluent API配置。將配置封裝到一個EntityTypeConfiguration類中和直接調用modelBuilder.Configurations的OnModelCreating方法效果是一樣。只是為了讓事情井井有條,我們將創建一個單獨的配置類,示例5-15中所示。


Example 5-15. An empty configuration class for Reservation

public class ReservationConfiguration :
  EntityTypeConfiguration<Reservation>
{
}

注意我們除了聲明這個類以外什么都沒做。里面還沒有代碼。這足以讓你向DbModelBuilder配置中添加類,確保Reservation包含進模型中將映射到數據庫Reservations表。

modelBuilder.Configurations.Add(new ReservationConfiguration());

現在你已經看到Code First包含一個類到模型的默認方式,現在我們來學習如何配置模型來排降一個類。

避免類型包含進模型中


您可能在應用程序中為了某些目的定義了一些類,但並不要求它些在數據庫被持久化。即使你不為它們定義一個DbSet或任何配置,它們也可能的被另一種已經映射的類型所訪問而被拉入模型,在查詢或更新數據庫時被非預期創建在數據庫中。

但是,您可以明確地告訴Code First忽略一個類,不要使其成為模型的一部分。

使用Data Annotations 來忽略類型

NotMapped特性標記可以用來指導Code First從模型中排隊類型:

[NotMapped]
public class MyInMemoryOnlyClass
使用Fluent API配置來忽略類型

使用Fluent API,你需要使用Ignore方法來避免類型被扯入模型中。如果你想忽略一個類,你需要直接從DbModelBuilder來實現,不能在EntityTypeConfigraiton內進行:

modelBuilder.Ignore<MyInMemoryOnlyClass>();
理解屬性映射和可訪問性

有各種因素可以影響你的類是否能被Code First所識別和映射。下面是一些在定義類時需要注意的規則,可以來了解默認規則需要什么,如何通過配置來改變默認映射。

標量屬性映射

如果屬性性可以轉化為EDM支持的類型稱為標量屬性,映射是唯一的。

合法的EDM類型有:Binary, Boolean, Byte, DateTime, DateTimeOffset, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, String, Time.

標量屬性不能映射到一個被忽略的EDMonton類型(如枚舉類型或無符號整型數);

屬性的可訪問性,Getters和Setters

1.public屬性將會被Code First自動映射。

2.Set訪問器可以用更嚴格的訪問規則界定,但get訪問器必須保持public才能被自動映射;

3.非公開的屬性必須使用Fluent API配置才能被Code First所映射;

對於非公開屬性,這意味只有執行配置的位置才能訪問該屬性。

例如,如果Person類有一個internal Name的屬性,使用與PersonContext類相同的程序集,你可以在Person context的OnModelCreating方法中調用modelBuilder.Entity<Person>().Property(p => p.Name)。這就會使用該屬性包含進你的方法。但是如果Person和PersonContext定義在單獨的程序集中,你就需要添加一個PersonConfiguration類(EntityConfiguration<Person>)到Person的同一程序集中,以執行配置類內的配置。這要求包含有域類的程序集必須添加對EntityFramework.dll的引用。PersonCOnfig配置類可以在PersonContext的OnModelCreating方法中被注冊。

類似的方法可以用於受保護的和私有的屬性。但是,配置類必須嵌套在類內部,成為模型的一部分,這樣才能訪問私有或受保護的屬性。下面是一個這樣的例子,使用private隱藏了Name屬性,但是允許外部代碼使用CreatePerson方法對Name屬性進行設置。嵌套的PersonConfig類可以訪問本地復制的Name屬性。

public class Person
{
  public int PersonId { get; set; }
  private string Name { get; set; }
  public class PersonConfig : EntityTypeConfiguration<Person>
  {
    public PersonConfig()
    {
      Property(b => b.Name);
    }
  }
  public string GetName()
  {
    return this.Name;
  }
  public static Person CreatePerson(string name)
  {
    return new Person { Name = name };
  }
}
當我們配置類為為嵌套類時,可以使用下列代碼:
modelBuilder.Configurations.Add(new Person.PersonConfig()); 
 

一個常見的場景是為了避免開發者在代碼中修改某個特定的屬性(如PersonId),使用set訪問器來將屬性設置為private或internal.這種場景的實現歸功於上述所列的第二個規則:Set訪問器可以用更嚴格的訪問規則界定,但get訪問器必須保持public才能被自動映射;EF框架必須使用反射才能訪問非公開的set訪問器,但當運行於中等信任的模式時這並不提供支持。除了中等信任的情況以外,這意味着當真實對象作為查詢或插入的結果時,上下文將能夠填充受限的屬性。上下文也能夠以查詢或插入的數據為屬性設置值--即使上下文和域類處於單獨的程序集或名稱空間里。這即可以工作在有鍵值的情況,也可以工作在沒有鍵值的情況下。

避免屬性被包含在模型中

默認規則里,所有同時擁有get和set訪問器的公開屬性都會包含進模型中。

Code First使用使用相同的配置方法—Data Annotations 的NotMapped標記或Fluent API的Ignore配置方法---來排除類中的屬性。

一個典型的不想儲存在數據庫中的屬性例子是在類中使用其他屬性計算出來的屬性。例如,你可能想很容易地訪問一個人的full name,這是根據First Name和Last Name合並計算得到的。類可以計算它而沒有必要將其存進數據庫。

如果一個屬性吸有get或set訪問器,也不會被包含進模型中。

如果你在Person類中有FullName屬性,可以只設置get訪問器而不設置set訪問器來實現排除在映射之外。

public string FullName
{
   get { return String.Format("{0} {1}",
                              FirstName.Trim(), LastName); }
}

 

但是,你可以還會有一些同時擁有get和set訪問器的屬性不想持久化到數據庫。例如,Destination類可能會有一個字符串包含有當前的天氣預報。但你可能想在程序的什么地方彈出預報內容,而不想對預報本身作出什么探究。

private string _todayForecast;
public string TodayForecast
{
  get { return _todayForecast; }
  set { _todayForecast = value; }
}

這種情況下你不想持久化天氣預報信息到數據庫。EF框架在執行映射到Destination的查詢或修改數據庫表的時候不能包含這個屬性。

使用Data Annotations 忽略屬性

使用Data Annotations ,必須應用NotMapped特性標記:

[NotMapped]
public string TodayForecast
使用Fluent API忽略屬性

在Fluent API,你需要配置實體忽略一個屬性。下列是一個在DestinationConfiguration類中使用Ignore方法的例子:

Ignore(d => d.TodayForecast);

注意:在使用NotMapped或Ignore對private屬性進行設置時有一個已知的bug。你可以MSDN找到關於此bug的描述。2011-8-18,微軟評論聲稱:此bug已修復,並且會在下一個主要版本的Code First中發布。


映射到繼承層次結構

EF框架支持各種模型中的繼承層次結構。無論你使用Code First,Model First還是Database First來定義模型都不用擔心繼承的類型問題,也不用考慮EF框架如何使用這些類型進行查詢,跟蹤變更和更新數據。

使用Code First的默認繼承:每層次結構映射表(TPH)


TPH描述了映射繼承類關系到獨立數據庫表的方法,這種方法使用鑒別列來識別是否為子類型。這是Code First默認規則使用的表映射方法。為了觀察這種行為,我們對模型做兩處修改。首先我們從Lodging類里移除IsResort屬性,然后創建一個單獨的Resort類繼承自Lodging類。代碼5-16顯示了這些類:

Example 5-16. Modified Lodging class and a new Resort class that derives from Lodging

public class Lodging
{
  public int LodgingId { get; set; }
  [Required]
  [MaxLength(200)]
  [MinLength(10)]
  public string Name { get; set; }
  public string Owner { get; set; }
  // public bool IsResort { get; set; }
  public decimal MilesFromNearestAirport { get; set; }
  [InverseProperty("PrimaryContactFor")]
  public Person PrimaryContact { get; set; }
  [InverseProperty("SecondaryContactFor")]
  public Person SecondaryContact { get; set; }
  public int DestinationId { get; set; }
  public Destination Destination { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
}
public class Resort : Lodging
{
  public string Entertainment { get; set; }
  public string Activities { get; set; }
}


圖5-6顯示了數據庫使用默認規則后的變化:

image


Resort信息儲存在Lodgings表中,Code First創建了一個列命名為Didcriminator。注意這是一個非可空列,類型為nvarchar(128)。默認情況下,Code First會使用每個類型在繼承層次中的類名作為discrimnator列的存儲值。例如,如果你添加和運行InsertLogdging方法(代碼5-17),由EF框架生成的INSERT語句會將字符串“Lodging”放進新加入行的Discriminator列中。

Example 5-17. Code to insert a new Lodging type

private static void InsertLodging()
{
  var lodging = new Lodging 
  {
    Name = "Rainy Day Motel",
    Destination=new Destination
    {
      Name="Seattle, Washington",
      Country="USA"
    }
   };
  using (var context = new BreakAwayContext())
  {
    context.Lodgings.Add(lodging);
    context.SaveChanges();
  }
}

作為可選方案,代碼5-18顯示了一個新的Resort類型的實例:

Example 5-18. Code to insert a new Resort type

private static void InsertResort()
{
  var resort = new Resort {
    Name = "Top Notch Resort and Spa",
    MilesFromNearestAirport=30,
    Activities="Spa, Hiking, Skiing, Ballooning",
    Destination=new Destination{
                Name="Stowe, Vermont",
                Country="USA"}
    };
  using (var context = new BreakAwayContext())
  {
    context.Lodgings.Add(resort);
    context.SaveChanges();
  }
}

這一次,EF框架將會在Discriminator列中插入字符串“Resort”.

這種默認規則的行為基於你可能會加入更多的衍生Lodging類型。如果Lodging類只有有Resort和沒有這兩種情況,此時discrimainator列就可指定為Boolen型,同時也就沒有擴展層次的空間了。這種靈活性在默認規則中工作得很好。

使用Fluent API定制TPH區分符字段

你可以通過指定配置來定制discriminator列的類型和命名,方法是使用Fluent API(Data Annotations 沒有標記可用於定制TPH)。

使用Map配置方法來實現,前已述及,Map可用於實體分割。這個方法需要包含幾個配置:

Example 5-19. Configuring the discriminator column name and possible values

 

Map(m =>
    {
        m.ToTable("Lodgings");
        m.Requires("LodgingType").HasValue("Standard");
    })
.Map<Resort>(m =>
    {
        m.Requires("LodgingType").HasValue("Resort");
    });

注意我們看到了幾個新的配置方法-Requires和HasValue.Requires是一個配置,在此用於指定一個discriminator列。HasValue也用於指定配置discriminator.你可以使用HasValue來指定在一個特定類型中使用什么樣的值。我們告訴Code First使用LodgingType作為discriminator列的列名而不是使用默認的名稱:Discriminator.默認規則規定,Code First使用類名作為Discriminator值(例如:“Lodging”).我們將其調整為“Standard”(Lodging基類適用)和“Resort”(派生類適用)。

你可能知道Loding的唯一派生類就是Resort,因此決定使用Boolean型數據,如用IsResort表示,這也是可以的。在這種情況下,值將是Boolean型。你不需要告知Code First這個事實,只需要在提供期望的值即可,Code First將正確地識別discriminator列的類型。

修改為布爾型的discriminator的代碼見代碼5-20.

Example 5-20. Configuring a discriminator column to be a boolean

Map(m =>
{
  m.ToTable("Lodging");
  m.Requires("IsResort").HasValue(false);
})
.Map<Resort>(m =>
{
  m.Requires("IsResort").HasValue(true);
});

結果見圖5-7:

image

 

配置每個類型映射表(TPT)

      TPH將所有層次的類都放在了一個表里,而TPT在一個單獨的表中儲存來自基類的屬性。在派生類定義的附加屬性儲存在另一個表里,並使用外鍵與主表相連接。如果你的數據庫構架使用單獨的表表達層次關系,你需要對派生類進行顯示的配置。下面是一個簡單配置的例子,你需要指定派生類的表名,可以使用Data Annotations 也可以使用Fluent API來完成這項工作。

下面是對Resort類型的配置:

[Table("Resorts")]
public class Resort : Lodging
{
  public string Entertainment { get; set; }
  public string Activities { get; set; }
}


聯合使用繼承和Data Annotation 的Table特性標記將告知Code First要為Resort類型創建一個新表,由於該表繼承自Lodging,它將繼承Lodging的鍵屬性。


圖5-8顯示了Lodgings和新建的Resorts表。注意:Lodgings不再包含discriminator也沒有Resort的字段(Entertainment and Activities).

image


新增的Resorts表中有一個LodgingId類,這是一個主鍵也是一個外鍵,后者的名字被區分為:Resort_TypeConstraint_From_Lodging_To_Resorts.


值得好奇的是,在Resort_Type Constraint_From_Lodging_To_Resorts 鍵上沒有定義級聯刪除。EF框架將在必要的時候來維護刪除的數據。

當你添加一個新的Resort並調用 SaveChanges方法時,就觸發EF框架首先在Lodging表添加以合適的值,然后返回一個新LodgingId值,然后使用這個LodgingId值在Resorts表中插入一個新行。

使用Fluent API你可以使用ToTable方法來獲得TPT映射。你只需要指定派生實體的表名,Resort,這樣Code First就會創建附加表和約束,如您在較長5-8所見。代碼5-21顯示了只接通過modelBuilder實例調用的例子。

Example 5-21. In-line ToTable mapping used for TPT inheritance

modelBuilder.Entity<Resort>().ToTable("Resorts");


你也可以從基類開始配置,使用Map方法得到Resort類型:
Example 5-22. Mapping ToTable within the Map method

modelBuilder.Entity<Lodging>()
   .Map<Resort>(m =>
   {
     m.ToTable("Resorts");
   }
  );

如果你想顯示操作,你可以指定每個層級的表名。在這種情況下,Lodgings表已經被默認規則所指定,但是為了更明確的配置,我們使用了更為清晰的代碼,如代碼5-23:

Example 5-23. Mapping ToTable for a TPT inheritance from base entity

modelBuilder.Entity<Lodging>().Map(m =>
   {
     m.ToTable("Lodgings");
   }).Map<Resort>(m =>
   {
     m.ToTable("Resorts");
   });

這最后的變化很有趣,你可以從它的基類配置映射的派生類。但無法從實體<Resort>開始,然后添加<Lodging>的映射。

創建這個映射的所有三個變化達到同樣的目的。如果需要,你可能想作出選擇以更好地適應您的編碼風格。

Configuring for Table Per Concrete Type (TPC) Inheritance

 

配置每概念層級類型一個表(TPC)

     TPC類似於TPT,除了每個類型的所有屬性都儲存在單獨表里以外。在層級中沒有一個核心表包含通用於所有類型的數據。這就使用需要映射一個繼承結構層次到層疊(通用)的字段上。這對你將歷史數據存存儲在備用表時特別有用。可我們會將Lodgings和Resorts映射到一個resort表里,這個表里也包含Name,Ownert MilesFromNearestAirport字段。你可以使用Fluent API來配置你的結構層次映射到這樣的表中。(Data Annotations 不支持TPC)。

 

    下面對Lodging/Resort結構層次再做些改變。

     TPC使用MapInheritedProperties方法來進行配置,只能在Map方法里才能訪問。既然我們需要為派生類提供單獨的表(這會復制繼承的屬性),我們需要聯合設置Table和MapInheritedProperties方法來進行配置。

     注意你必須使用ToTable方法包含到基類實體的映射。雖然這在TPT不是必須的,但TPC是:

modelBuilder.Entity<Lodging>()
 .Map(m =>
 {
   m.ToTable("Lodgings");
 })
 .Map<Resort>(m =>
 {
   m.ToTable("Resorts");
   m.MapInheritedProperties();
 });

MapInheritedProperties方法告知Code First它想要重映射所有繼承自基類的屬性到派生類表的新列里。

避免TPC里的映射異常


as a reminder in Example 5-24.

如果你嘗試運行當前程序來檢查配置,你會得到一個異常,描述存在映射沖突,因為DbModelBuilder 嘗試創建新模型。這個沖突來自於Lodging類。

TPC要求任何TPC層級內的類通過一個顯示定義的外鍵屬性來建立關系。觀察Lodging類,如代碼5-24所示:

Example 5-24. A reminder of the Lodging class

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public decimal MilesFromNearestAirport { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Person PrimaryContact { get; set; }
  public Person SecondaryContact { get; set; }
  public int DestinationId { get; set; }
  public Destination Destination { get; set; }
}

While the navigation to Destination is complemented by the DestinationId property, there are two navigation reference properties that do not have a foreign key  property: PrimaryContact and SecondaryContact. Code First leverages the database foreign key fields to take care of persisting the relationship. If you’ve been using Entity Framework since the first version, you may recognize this as independent associations, which were the only option for building relationships in Visual Studio 2008. Foreign Key associations, where we can have a foreign key property such as DestinationId in the class, were introduced to Entity Framework in Visual Studio 2010 and .NET 4. TPC can’t work with classes that have independent associations in them.

雖然到Destinaition的導航,是由DestinationId屬性補充的,雖然有兩個導航引用屬性,但是沒有外鍵屬性:PrimaryContact和SecondaryContact。Code First利用數據庫外鍵字段來維護關系的持久化。如果您使用過EF框架的第一個版本,您可以將此視為獨立的關系,是Visual Studio 2008中建立關系的唯一選項。而外鍵關系,在這里我們可以使用外鍵屬性如類中的DestinationId屬性,被引入到Visual Studio 2010和.NET4下的EF框架中。 TPC不能與帶有獨立關聯的類一同工作。(譯者注:此處不知所雲,請高手賜教!)

      為了解決這個問題,你必須給Lodging添加外鍵屬性。對某些開發人員而言,這是一個被迫吞下的痛苦的葯丸---為了讓類加入而被迫遵守EF框架的約定。不幸的是,正如您可能已經關注到的,實現Code First的很多映射就需要有外鍵可用。

      請記住,在我們的域中,可能是一個Lodging既沒有PrimaryContact,也沒有SecondaryContact。當我們在第4章增加PrimaryContact和SecondaryContact導航屬性時,Code First通過約定推斷它們是可空類型(Optional)。新的外鍵的屬性是整數,默認情況下,非可空。這樣沖突就出現了,因為如果在外鍵中有一個值,Contact就不是可選的。因此,我們將創建新的具有可空性的外鍵屬性。注意使用Nullable<T>泛型來創建PrimaryContactId和SecondaryContactId兩個新屬性,如代碼5-25所示。


Example 5-25. Lodging class with nullable foreign keys

abstract public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public decimal MilesFromNearestAirport { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Nullable<int> PrimaryContactId { get; set; }
  public Person PrimaryContact { get; set; }
  public Nullable<int> SecondaryContactId { get; set; }
  public Person SecondaryContact { get; set; }
  public int DestinationId { get; set; }
  public Destination Destination { get; set; }
}

 

       我們還沒有完成。記住Code First無法在沒有協助的情況下識別出非常規的外鍵屬性。這些新的屬性不匹配任何三種Code First檢測外鍵的可能模式(例如,PersonId)。所以你需要使用HasForeignKey來進行映射,如同例4-3。
代碼5-26顯示了對這些屬性在LodgingConfiguration類的兩個現有配置。我們通過加入HasForeignKey映射對其進行了修改。

Example 5-26. Fixing up the model for unconventional foreign key properties

HasOptional(l => l.PrimaryContact)
  .WithMany(p => p.PrimaryContactFor)
  .HasForeignKey(p=>p.PrimaryContactId);
HasOptional(l => l.SecondaryContact)
  .WithMany(p => p.SecondaryContactFor)
  .HasForeignKey(p => p.SecondaryContactId);

最后所有的片斷都安置在TPC所需的正確位置。模型將會對其進行驗證,如圖5-9所示。事實上,所有自Lodging類的繼承字段現在都在Resorts表中。由我們協助Code First識別外鍵,這些外鍵也在Resorts表中進行了合適的配置。

image

使用抽象基類

     所有我們前面使用的繼承都是基類為實體類的情況,我們也可以使用抽象類來進行工作。在使用Code First來構建抽象基類的模型之前我們先作一個快速的了解。

我們將Lodging類修改成為抽象基類。這意味着我們不再能直接使用Lodging類。它不能被實例化。我們只能使用派生於它的類。見例5-27,我們添加了第二個派生類:Hostel.

代碼5-27列出了所有的三個類。
Example 5-27. The abstract base class, Lodging, with its derived classes, Resort and Hostel

abstract public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public decimal MilesFromNearestAirport { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Nullable<int> PrimaryContactId { get; set; }
  public Person PrimaryContact { get; set; }
  public Nullable<int> SecondaryContactId { get; set; }
  public Person SecondaryContact { get; set; }
  public int DestinationId { get; set; }
  publicDestination Destination { get; set; }
}
public class Resort : Lodging
{
  public string Entertainment { get; set; }
  public string Activities { get; set; }
}
public class Hostel: Lodging
{
  public int MaxPersonsPerRoom { get; set; }
  public bool PrivateRoomsAvailable { get; set; }
}
 

當你將Lodging類變成抽象類,這意味着不能在程序中實例化Lodging類。如果控制台程序中有這樣的代碼將會導致編譯錯誤。因此你應該將有關代碼注釋掉。一個好的方法是將方法使用#if/#endif包括起來,例如:

#if false
private static void InsertLodging()
  {
    var lodging = new Lodging
    {
      Name = "Rainy Day Motel",
      Destination = new Destination
      {
        Name = "Seattle, Washington",
        Country = "USA"
      }
    };
    using (var context = new BreakAwayContext())
    {
      context.Lodgings.Add(lodging);
      context.SaveChanges();
    }
  }
#endif

為了重新使用代碼,可以將其改為#if true

我們已經移除了TPC的配置使模型和數據庫表完全基於Code First的默認配置,這表示繼承結構重新成為TPH。所有派生類的字段都會包含在Lodgings表中。在圖5-10中可以看到盡管Lodging類是一個抽象類,對數據庫的構建與其不是抽象類沒有什么影響。但是,既然我們有了另一個派生類,還是有一些新的屬性包含了進去,這些屬性是Hostel類型提供的。

image

 

代碼5-28顯示了一系列插入新的Resort和新的Hostel的方法,然后從數據庫中查出所有Lodgings來觀察。

Example 5-28. Code to insert a Resort, then insert a Hostel, and finally to query Lodgings

private static void InsertResort()
{
  var resort = new Resort
  {
    Name = "Top Notch Resort and Spa",
    MilesFromNearestAirport = 30,
    Activities = "Spa, Hiking, Skiing, Ballooning",
    Destination = new Destination { Name = "Stowe, Vermont",
                                    Country = "USA" }
                                  };
  using (var context = new BreakAwayContext())
  {
    context.Lodgings.Add(resort);
    context.SaveChanges();
  }
}
private static void InsertHostel()
{
  var hostel = new Hostel
  {
    Name = "AAA Budget Youth Hostel",
    MilesFromNearestAirport = 25,
    PrivateRoomsAvailable=false,
    Destination = new Destination {
                    Name = "Hanksville, Vermont",
                    Country = "USA" }
                  };
  using (var context = new BreakAwayContext())
  {
context.Lodgings.Add(hostel);
    context.SaveChanges();
  }
}
private static void GetAllLodgings()
{
  var context = new BreakAwayContext();
  var lodgings = context.Lodgings.ToList();
  foreach (var lodging in lodgings)
  {
    Console.WriteLine("Name: {0}  Type: {1}",
       lodging.Name, lodging.GetType().ToString());
  }
  Console.ReadKey();
}

當EF框架發送INSERT命令到數據庫,它使用“Resort”和“Hostel”分別填充了Discriminator列的數據。當調出所Lodgings的數據時,它過濾了Resort和Hostel識別標記,如代碼5-29所示的SQL語句:

Example 5-29. SQL to retrieve all of the known types that derive from Lodging

SELECT
[Extent1].[Discriminator] AS [Discriminator],
[Extent1].[LodgingId] AS [LodgingId],
[Extent1].[Name] AS [Name],
[Extent1].[Owner] AS [Owner],
[Extent1].[MilesFromNearestAirport] AS [MilesFromNearestAirport],
[Extent1].[PrimaryContactId] AS [PrimaryContactId],
[Extent1].[SecondaryContactId] AS [SecondaryContactId],
[Extent1].[DestinationId] AS [DestinationId],
[Extent1].[Entertainment] AS [Entertainment],
[Extent1].[Activities] AS [Activities],
[Extent1].[MaxPersonsPerRoom] AS [MaxPersonsPerRoom],
[Extent1].[PrivateRoomsAvailable] AS [PrivateRoomsAvailable]
FROM [dbo].[Lodgings] AS [Extent1]
WHERE [Extent1].[Discriminator] IN ('Resort','Hostel')

為什么要使用discriminators而不是簡單地返回所有的Lodging數據?這覆蓋了其他類型在數據庫中不是模型的一部分的場景。

圖5-11顯示了前兩個方法插入數據后調用GetAllLodgings方法的控制台輸出結果。

image

      可以修改映射變成TPT或TPC。例如,如果你同時給Resort和Hostel類指定表明而Lodging類是抽象的,你就會得到三個數據庫表,Resorts,Hosetls和Lodgings。代碼5-28在沒有任何修改的情況下可以正常工作。SQL命令將在必要時組合內聯有關表,正如Lodging不是抽象的一樣。所有模型中圍繞着抽象基類的行為都簡單遵從了EF框架第一版以來的行為。唯一的區別就是以新的方式定義了模型。

現在我們已經探索了抽象基類,現在將將abstract關鍵字從Lodging類移走,以便我們可以再次創建Lodging類的實例。也可將前面已經注釋掉的任何方法重新啟用:

public class Lodging


映射關系

到目前為止我們已經掌握了如何控制類或其屬性的映射方法;最后我們來看看關系如何來映射.這包括控制外鍵列的命名以及多對對關系內聯表的命名.第4章對關系的默認規則和配置進行了全面介紹.你應該對映射已經很熟悉了,這一部分將提供一些方式去控制映射的一些細節部分.

控制包含在類中的外鍵

你已經看到兩個類的關系通過添加導航屬性得到了創建.可以選擇性地在一個單獨類中設置外鍵.默認情況,Code First將會使用屬性名作為列名.在第4章我們了解到,當添加DestinationId到Lodging類,Code First將添加了一個DestinationId列到數據庫里並將其配置為一個外鍵 .

將外鍵屬性的列名進行更改類似於更改其他屬性的列名.改變外鍵屬性名不會影響Code First檢查是否為外鍵的能力.外鍵檢測只針對屬性名而不是屬性映射到數據庫的列名.

假如想把列名更為destination_id.你可以使用Column的Data Annotations 標記直接將外鍵屬性進行更改.

[Column("destination_id")]
public int DestinationId { get; set; }

當然也可使用Fluent API來配置LodgingConfiguration類達成同樣的目的:

Property(l => l.DestinationId).HasColumnName("destination_id");
控制由Code First生產的外鍵

如果類中沒有包含外鍵屬性Code First就會自動創建一下.例Lodging類,一個Destination_DestinationId列就被加到數據庫中,就是一個自動添加的外鍵列.我們從Lodging類中移走DestinationId外鍵屬性,讓Code First自動創建一下.(如代碼3-10).

Example 5-30. Foreign key property commented out

public class Lodging
{
  public int LodgingId { get; set; }
  public string Name { get; set; }
  public string Owner { get; set; }
  public decimal MilesFromNearestAirport { get; set; }
  //public int DestinationId { get; set; }
  public Destination Destination { get; set; }
  public List<InternetSpecial> InternetSpecials { get; set; }
  public Nullable<int> PrimaryContactId { get; set; }
  public Person PrimaryContact { get; set; }
  public Nullable<int> SecondaryContactId { get; set; }
  public Person SecondaryContact { get; set; }
}

變更外鍵列名只能通過Fluent API.你可以使用Map方法來控制的映射,也可使用Map方法來控制關系的映射.

代碼5-31顯示了如何添加Map方法到關系來給外鍵名進行指定.

Example 5-31. Generated foreign key column configured

HasRequired(l => l.Destination)
  .WithMany(d => d.Lodgings)
  .Map(c => c.MapKey("destination_id"));

現在你就已經觀察到這些行為.將所有注釋掉的代碼恢復過來,移除上述Fluent API配置.你也可以取消DeleteDestinationInMemoryAndDbCascade方法的注釋:

public int DestinationId { get; set; }
public Destination Destination { get; set; }
控制使用實體切分生成的外鍵

前面已經學習過有關實體拆分的技術。可以讓一個類分配其屬性到多個數據庫表中。在此可以按意圖對表中添加的外鍵列進行控制。

默認情況,外鍵將添加到你在實體分割配置中指定的第一個表中。你可以通過附加在生成外鍵的ToTable方法后來變更這種情況。例如,將Lodging實體划分為Lodgings表和LodgingInfo表。如果想要將外鍵放在LodgingInfo表的相關destination上,你將應按如下方式添加配置:

Example 5-32. Generated foreign key column configured

HasRequired(l => l.Destination)
  .WithMany(d => d.Lodgings)
  .Map(c => c.MapKey("destination_id").ToTable("LodgingInfo"));
控制多對多關系中的內聯表

前面我們已經在Acitvity和Trip之間引入了多對多關系,最終在數據庫中生成ActivityTrips內聯表(見圖4-10)。

但是,在我們的域中將表名命名為TripActivites可能含義更明了。幸運的是可以使用Map方法配置這個表名。添加的配置設置見代碼5-33:

Example 5-33. Many-to-many join table name changed

HasMany(t => t.Activities)
  .WithMany(a => a.Trips)
  .Map(c => c.ToTable("TripActivities"));

映射首先使用HasMany和WithMany方法來識別要配置的關系。一旦關系得到識別,就可以使用Map方法來指定映射。在映射關系中,你可使用ToTable方法來指定表名。我們來看看Map方法最終得到的結果。內聯表更新后如圖5-12所示:

image

你可以想要外鍵名叫做TripIdentifier和ActivityId.可以通過Map方法來實現列名的指定。

Example 5-34. Changing the many-to-many column names

HasMany(t => t.Activities)
  .WithMany(a => a.Trips)
  .Map(c =>
    {
      c.ToTable("TripActivities");
      c.MapLeftKey("TripIdentifier");
      c.MapRightKey("ActivityId");
    });

注意MapLeftKey和MapRightKey方法用來指定列名。MapLeftKey影響指向配置的類的外鍵。你可以添加配置到TripConfiguration類,因此Trips是被配置的實體。這樣Trip被視為左實體,Activity視為右實體。圖5-13顯示了更改列名后的效果:

image

小結

本章學習了Code First控制類與屬性映射到數據庫的技術。通過學習,您應掌握控制列,表乃至構架的方法。還學習了如何配置類映射到多個表以及如何將多個類指向單一的表的技術。然后花了很多時間研究了如何配置繼承層次關系 ,很多配置只能使用Fluent API 來進行,最后你還學到如何調整到關系的映射。

總之,本章所學已經能夠使Code First實現使用EDMX進行設計所實現的幾乎全部功能 。通過Code Fi,你有能力將域類插入到EF框架中,而不使用設計器或任何附加模型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM