具有建構函式的實體類型

您可以使用參數定義建構函式,並在建立實體的實例時讓 EF Core 呼叫此建構函式。 建構函式參數可以系結至對應的屬性,或系結至各種服務,以加速延遲載入等行為。

注意

目前,所有建構函式系結都是依慣例進行的。 計畫在未來版本中設定要使用的特定建構函式。

系結至對應的屬性

請考慮典型的部落格/文章模型:

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

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

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

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

當 EF Core 建立這些類型的實例,例如查詢的結果時,它會先呼叫預設無參數建構函式,然後將每個屬性設定為資料庫中的值。 不過,如果 EF Core 找到參數化建構函式具有符合對應屬性的參數名稱和類型,則會改為使用這些屬性的值呼叫參數化建構函式,而不會明確設定每個屬性。 例如:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; set; }

    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; set; }

    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

要注意的事項:

  • 並非所有屬性都需要有建構函式參數。 例如,Post.Content 屬性不是由任何建構函式參數所設定,因此 EF Core 會在以一般方式呼叫建構函式之後加以設定。
  • 參數類型和名稱必須符合屬性類型和名稱,不同之處在于屬性在參數大小寫時可以是 Pascal 大小寫。
  • EF Core 無法使用建構函式來設定導覽屬性(例如上述部落格或文章)。
  • 建構函式可以是公用、私人或具有任何其他協助工具。 不過,延遲載入 Proxy 需要從繼承的 Proxy 類別存取建構函式。 這通常表示將其設定為公用或受保護。

唯讀屬性

一旦透過建構函式設定屬性,讓其中一些屬性成為唯讀,就有意義。 EF Core 支援這項功能,但有一些需要注意的事項:

  • 沒有 setter 的屬性不會依慣例對應。 (這樣做通常會對應不應對應的屬性,例如計算屬性。
  • 使用自動產生的索引鍵值需要可讀寫的索引鍵屬性,因為金鑰值必須在插入新實體時由金鑰產生器設定。

避免這些事項的簡單方式是使用私人 setter。 例如:

public class Blog
{
    public Blog(int id, string name, string author)
    {
        Id = id;
        Name = name;
        Author = author;
    }

    public int Id { get; private set; }

    public string Name { get; private set; }
    public string Author { get; private set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    public Post(int id, string title, DateTime postedOn)
    {
        Id = id;
        Title = title;
        PostedOn = postedOn;
    }

    public int Id { get; private set; }

    public string Title { get; private set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; private set; }

    public Blog Blog { get; set; }
}

EF Core 會將具有私用 setter 的屬性視為讀寫,這表示所有屬性都與之前一樣對應,而且金鑰仍可儲存產生。

使用私人 setter 的替代方法是讓屬性真正唯讀,並在 OnModelCreating 中新增更明確的對應。 同樣地,某些屬性可以完全移除,並只以欄位取代。 例如,請考慮下列實體類型:

public class Blog
{
    private int _id;

    public Blog(string name, string author)
    {
        Name = name;
        Author = author;
    }

    public string Name { get; }
    public string Author { get; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

public class Post
{
    private int _id;

    public Post(string title, DateTime postedOn)
    {
        Title = title;
        PostedOn = postedOn;
    }

    public string Title { get; }
    public string Content { get; set; }
    public DateTime PostedOn { get; }

    public Blog Blog { get; set; }
}

以及 OnModelCreating 中的此組態:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Author);
            b.Property(e => e.Name);
        });

    modelBuilder.Entity<Post>(
        b =>
        {
            b.HasKey("_id");
            b.Property(e => e.Title);
            b.Property(e => e.PostedOn);
        });
}

注意事項:

  • 索引鍵 「property」 現在是欄位。 這不是欄位 readonly ,因此可以使用預存產生的金鑰。
  • 其他屬性只是在建構函式中設定的唯讀屬性。
  • 如果只有 EF 設定主鍵值或從資料庫讀取,則不需要將它包含在建構函式中。 這會將索引鍵 「屬性」保留為簡單欄位,並清楚指出在建立新的部落格或文章時,不應明確設定。

注意

此程式碼會產生編譯器警告 '169',指出永遠不會使用欄位。 這可以忽略,因為事實上 EF Core 會以外語方式使用 欄位。

插入服務

EF Core 也可以將「服務」插入實體類型的建構函式。 例如,可以插入下列專案:

注意

目前,只能插入 EF Core 已知的服務。 未來版本會考慮插入應用程式服務的支援。

例如,插入的 DbCoNtext 可用來選擇性地存取資料庫,以取得相關實體的相關資訊,而不需要載入所有實體。 在下列範例中,這會用來取得部落格中的文章數目,而不需要載入文章:

public class Blog
{
    public Blog()
    {
    }

    private Blog(BloggingContext context)
    {
        Context = context;
    }

    private BloggingContext Context { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }

    public ICollection<Post> Posts { get; set; }

    public int PostsCount
        => Posts?.Count
           ?? Context?.Set<Post>().Count(p => Id == EF.Property<int?>(p, "BlogId"))
           ?? 0;
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PostedOn { get; set; }

    public Blog Blog { get; set; }
}

需要注意的一些事項:

  • 建構函式是私用的,因為它只由 EF Core 呼叫,而且有另一個公用建構函式可供一般使用。
  • 使用插入服務的程式碼(也就是內容)是防禦性的,就是 null 處理 EF Core 未建立實例的情況。
  • 因為服務會儲存在讀取/寫入屬性中,所以當實體附加至新的內容實例時,將會重設服務。

警告

插入這類 DbCoNtext 通常被視為反模式,因為它會將實體類型直接結合至 EF Core。 請先仔細考慮所有選項,再像這樣使用服務插入。