共用方式為


一對一關聯性

當一個實體最多與另一個實體相關聯時,會使用一對一關聯性。 例如, Blog 有一個 BlogHeader,且 BlogHeader 屬於單 Blog一 。

本檔是以許多範例為結構。 這些範例會從常見案例開始,這也會介紹概念。 稍後的範例涵蓋較不常見的組態類型。 這裡的一個很好的方法是瞭解前幾個範例和概念,然後根據您的特定需求移至稍後的範例。 根據這種方法,我們將從簡單的「必要」和「選擇性」一對一關聯性開始。

提示

下列所有範例的程式代碼都可以在 OneToOne.cs中找到。

必要的一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

一對一關係是由下列專案所組成:

提示

不一定很明顯,一對一關係哪一端應該是主體,哪一端應該是相依的。 一些考慮包括:

  • 如果兩種類型的資料庫數據表已經存在,則具有外鍵數據行的數據表必須對應至相依型別。
  • 如果型別在沒有其他類型的情況下無法以邏輯方式存在,則類型通常是相依型別。 例如,對於不存在的部落格來說,擁有標頭並無意義,因此 BlogHeader 自然是相依類型。
  • 如果有自然父系/子系關聯性,則子系通常是相依型別。

因此,針對此範例中的關聯性:

  • 外鍵屬性 BlogHeader.BlogId 不可為 Null。 這會使關聯性「必要」,因為每個相依專案 (BlogHeader都必須與某些主體 相關,Blog因為其外鍵屬性必須設定為某些值。
  • 這兩個實體都有指向關聯性另一端相關實體的導覽。

注意

必要的關聯性可確保每個相依實體都必須與某些主體實體相關聯。 不過,主體實體一律可以存在,而不需要任何相依實體。 也就是說,必要的關聯性並不表示一律會有相依實體。 EF 模型沒有任何方法,也沒有任何標準方式在關係資料庫中,以確保主體與相依性相關聯。 如果需要,則必須在應用程式(商業)邏輯中實作它。 如需詳細資訊,請參閱必要導覽。

提示

具有兩個導覽的關聯性-一個從相依至主體,另一個從主體反轉為相依--稱為雙向關聯性。

此關聯性是由 慣例所探索。 也就是說:

  • Blog 會探索為關聯性中的主體,並 BlogHeader 探索為相依專案。
  • BlogHeader.BlogId 探索為參考 Blog.Id 主體主鍵之相依的外鍵。 因為不可為 Null,因此 BlogHeader.BlogId 會視需要探索關聯性。
  • Blog.BlogHeader 探索為參考導覽。
  • BlogHeader.Blog 探索為參考導覽。

重要

使用 C# 可為 Null 的參考型別時,如果外鍵屬性可為 Null,則從相依至主體的導覽必須可為 Null。 如果外鍵屬性不可為 Null,則導覽可能可為 Null 或不可為 Null。 在此情況下, BlogHeader.BlogId 是不可為 Null 的, BlogHeader.Blog 而且也是不可為 Null 的。 建 = null!; 構是用來將此標示為 C# 編譯程式的刻意,因為 EF 通常會設定 Blog 實例,而且對於完整載入的關聯性而言,它不可以是 Null。 如需詳細資訊,請參閱 使用可為 Null 的參考型 別。

如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

在上述範例中,關聯性的組態會啟動主體實體類型 (Blog)。 如同所有關聯性,它完全相當於以相依實體類型 (BlogHeader) 開頭。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

這兩個選項都比另一個選項都好;兩者都會產生完全相同的組態。

提示

絕對不需要設定關聯性兩次,一旦從主體開始,然後再從相依項目開始。 此外,嘗試個別設定關聯性的主體和相依部分通常無法運作。 選擇從一端或另一端設定每個關聯性,然後只撰寫組態程式代碼一次。

選擇性的一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

這與上一個範例相同,不同之處在於外鍵屬性和流覽至主體現在可為 Null。 這會使關聯性「選擇性」,因為相依性 (BlogHeader) 無法透過設定其外鍵屬性並巡覽至 null,與任何主體 (Blog) 相關

重要

使用 C# 可為 Null 的參考型別時,如果外鍵屬性可為 Null,則從相依至主體的導覽屬性必須可為 Null。 在此情況下, BlogHeader.BlogId 可為 Null,因此 BlogHeader.Blog 也必須可為 Null。 如需詳細資訊,請參閱 使用可為 Null 的參考型 別。

和之前一樣,此關聯性是 依慣例探索的。 如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired(false);
}

具有主鍵對主鍵關聯性的必要一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

與一對多關聯性不同,一對一關聯性的相依端可能會使用其主鍵屬性或屬性做為外鍵屬性或屬性。 這通常稱為 PK 對 PK 關聯性。 只有當主體和相依型別具有相同的主鍵類型,而且一律需要產生的關聯性時,才能這麼做,因為相依性的主鍵不可為 Null。

任何未依照慣例探索外鍵的一對一關聯性,都必須設定為指出關聯性的主體和相依端。 這通常是透過呼叫 HasForeignKey來完成。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>();
}

提示

HasPrincipalKey 也可用於此目的,但這樣做較不常見。

當呼叫 HasForeignKey中未指定任何屬性,且主鍵適合時,它會當做外鍵使用。 如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.Id)
        .IsRequired();
}

具有陰影外鍵的必要一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

在某些情況下,您可能不希望模型中的外鍵屬性,因為外鍵是資料庫中關聯性呈現方式的詳細數據,這在以純面向物件的方式使用關聯性時並不需要。 不過,如果要串行化實體,例如透過網路傳送,則當實體不在物件窗體中時,外鍵值可能會是保持關聯性資訊完好無損的有用方式。 因此,為了達到此目的,將外鍵屬性保留在 .NET 類型中通常是務實的。 外鍵屬性可以是私用的,這通常是很好的妥協,以避免公開外鍵,同時允許其值與實體一起移動。

在上一個範例之後,這個範例會從相依實體類型中移除外鍵屬性。 不過,EF 不是使用主鍵,而是指示建立稱為 BlogId 類型的int陰影外鍵屬性

此處要注意的重點是, 正在使用 C# 可為 Null 的參考型 別,因此,從相依至主體的導覽可 Null 性可用來判斷外鍵屬性是否可為 Null,因此關聯性是否為選擇性或必要。 如果未使用可為 Null 的參考型別,則陰影外鍵屬性預設會是可為 Null 的,因此關聯性預設為選擇性。 在此情況下,請使用 IsRequired 強制陰影外鍵屬性不可為 Null,並要求關聯性。

此關聯性再次需要一些組態來指出主體和相依性結束:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

具有陰影外鍵的選擇性一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

如同上述範例,外鍵屬性已從相依實體類型中移除。 不過,不同於前一個範例,這次外鍵屬性會建立為可為 Null,因為 正在使用 C# 可為 Null 的參考型 別,而相依實體類型的導覽是可為 Null 的。 這會讓關聯性成為選擇性的。

未使用 C# 可為 Null 的參考型別時,預設會將外鍵屬性建立為可為 Null。 這表示自動建立陰影屬性的關聯性預設為選擇性。

和之前一樣,此關聯性需要一些組態來指出主體和相依性結束:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired(false);
}

一對一但不流覽至主體

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

在此範例中,已重新導入外鍵屬性,但已移除相依專案的導覽。

提示

只有一個導覽的關聯性-一個從相依至主體,或一個從主體到相依,但不是兩者之間的關聯性,稱為單向關聯性。

此關聯性是由 慣例所探索,因為已探索外鍵,因此表示相依端。 如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

請注意,對 WithOne 的呼叫沒有自變數。 這是告訴 EF 沒有從 BlogHeader 巡覽至 Blog的方式。

如果組態從沒有導覽的實體開始,則必須使用泛型 HasOne<>() 呼叫明確指定關聯性另一端的實體類型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne<Blog>()
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

一對一沒有流覽至主體,且具有陰影外鍵

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
}

這個範例會藉由移除相依的外鍵屬性和巡覽,結合上述兩個範例。

和之前一樣,此關聯性需要一些組態來指出主體和相依性結束:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

更完整的組態可用來明確設定導覽和外鍵名稱,並視需要呼叫 IsRequired()IsRequired(false) 。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

不流覽至相依的一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

上述兩個範例有從主體巡覽至相依專案,但不會從相依於主體巡覽。 在接下來的幾個範例中,會重新導入相依項目的導覽,而主體上的流覽會改為移除。

根據慣例,EF 會將此視為一對多關聯性。 需要一些最少的設定,才能讓它成為一對一:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne();
}

再次請注意, WithOne() 呼叫 時沒有自變數,表示沒有此方向的流覽。

如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

如果組態從沒有導覽的實體開始,則必須使用泛型 HasOne<>() 呼叫明確指定關聯性另一端的實體類型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

無導覽的一對一

有時候,設定沒有導覽的關聯性會很有用。 這類關聯性只能藉由直接變更外鍵值來操作。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

慣例不會探索此關聯性,因為沒有任何導覽指出這兩種類型相關。 它可以在 中 OnModelCreating明確設定。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne();
}

使用此組態時, BlogHeader.BlogId 屬性仍會依慣例偵測為外鍵,而且關聯性為「必要」,因為外鍵屬性不可為 Null。 讓外鍵屬性可為 Null,即可將關聯性設為「選擇性」。

此關聯性更完整的明確組態為::

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

具有替代索引鍵的一對一

到目前為止所有範例中,相依的外鍵屬性受限於主體上的主鍵屬性。 外鍵可以改為限制為不同的屬性,然後成為主體實體類型的替代索引鍵。 例如:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

依慣例不會探索此關聯性,因為 EF 一律會依慣例建立與主鍵的關聯性。 您可以使用 對HasPrincipalKey的呼叫明確設定OnModelCreating。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId);
}

HasPrincipalKey 可以與其他呼叫結合,以明確設定流覽、外鍵屬性和必要/選擇性本質。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

具有複合外鍵的一對一

到目前為止,在所有範例中,主體的主要或替代索引鍵屬性是由單一屬性所組成。 主要或替代索引鍵也可以形成一個以上的屬性,這些稱為 「複合索引鍵」。 當關聯性的主體具有複合索引鍵時,相依的外鍵也必須是具有相同屬性數目的複合索引鍵。 例如:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

此關聯性是由慣例所探索。 不過,只有在已明確設定複合索引鍵時,才會發現,因為不會自動探索複合索引鍵。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

重要

如果其中一個屬性值為 Null,則複合外鍵值會被視為 null 。 具有一個屬性 Null 且另一個非 Null 的複合外鍵不會被視為與具有相同值的主要或替代索引鍵的相符專案。 這兩者都會被視為 null

HasForeignKeyHasPrincipalKey 都可以用來明確指定具有多個屬性的索引鍵。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasOne(e => e.Header)
                .WithOne(e => e.Blog)
                .HasPrincipalKey<Blog>(e => new { e.Id1, e.Id2 })
                .HasForeignKey<BlogHeader>(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

提示

在上述程式代碼中,對 HasKeyHasOne 的呼叫已分組成巢狀產生器。 巢狀建置器不需要針對相同的實體類型呼叫 Entity<>() 多次,但功能上相當於呼叫 Entity<>() 多次。

不需要串聯刪除的必要一對一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

依照慣例,必要的關聯性會設定為 串聯刪除。 這是因為一旦刪除主體之後,相依性就不能存在於資料庫中。 資料庫可以設定為產生錯誤,通常會當機應用程式,而不是自動刪除已不存在的相依數據列。 這需要一些設定:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

自我參考一對一

在上述所有範例中,主體實體類型與相依實體類型不同。 情況不一定如此。 例如,在下列類型中,每個類型 Person 都選擇性地與另一個 Person相關。

public class Person
{
    public int Id { get; set; }

    public int? HusbandId { get; set; } // Optional foreign key property
    public Person? Husband { get; set; } // Optional reference navigation to principal
    public Person? Wife { get; set; } // Reference navigation to dependent
}

此關聯性是由 慣例所探索。 如果慣例不會探索關聯性的導覽、外鍵或必要/選擇性本質,則可以明確地設定這些專案。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOne(e => e.Husband)
        .WithOne(e => e.Wife)
        .HasForeignKey<Person>(e => e.HusbandId)
        .IsRequired(false);
}

注意

針對一對一自我參考關聯性,因為主體和相依實體類型相同,因此指定哪一個類型包含外鍵並不會釐清相依端。 在此情況下,以點從相依至主體所指定的 HasOne 流覽,以及以點為單位從主體到相依的 WithOne 導覽。