使用建構函式的實體類型Entity types with constructors

注意

這項功能是在 EF 核心 2.1 中新功能。This feature is new in EF Core 2.1.

從開始 EF 核心 2.1,您就可以定義參數的建構函式,並已建立實體的執行個體時呼叫這個建構函式的 EF 核心。Starting with EF Core 2.1, it is now possible to define a constructor with parameters and have EF Core call this constructor when creating an instance of the entity. 建構函式參數可以繫結至對應的屬性,或為各種類型的服務,以促進行為類似消極式載入。The constructor parameters can be bound to mapped properties, or to various kinds of services to facilitate behaviors like lazy-loading.

注意

EF 核心 2.1 中,為所有的建構函式繫結的慣例。As of EF Core 2.1, all constructor binding is by convention. 若要使用的特定建構函式的組態已規劃在未來的版本。Configuration of specific constructors to use is planned for a future release.

繫結至對應的屬性Binding to mapped properties

請考量一般的部落格文章/模型:Consider a typical Blog/Post model:

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 核心建立這些類型的執行個體時,例如結果的查詢時,它會先呼叫預設的無參數建構函式,然後設定每一個屬性的值從資料庫。When EF Core creates instances of these types, such as for the results of a query, it will first call the default parameterless constructor and then set each property to the value from the database. 不過,如果 EF 核心找到的參數化建構函式參數名稱和相符的型別對應屬性,則它會改為呼叫這些屬性值的參數化建構函式,並將未明確設定每個屬性。However, if EF Core finds a parameterized constructor with parameter names and types that match those of mapped properties, then it will instead call the parameterized constructor with values for those properties and will not set each property explicitly. 例如: For example:

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

請注意一些事項:Some things to note:

  • 並非所有屬性都必須具有建構函式參數。Not all properties need to have constructor parameters. 比方說,Post.Content 是未設定屬性的任何建構函式參數,因此 EF 核心之後呼叫建構函式以一般方式將設定。For example, the Post.Content property is not set by any constructor parameter, so EF Core will set it after calling the constructor in the normal way.
  • 參數型別和名稱必須符合屬性類型和名稱,不同之處在於屬性可以是依照 pascal 命名法的大小寫慣例而參數是依照 camel 命名法的大小寫慣例。The parameter types and names must match property types and names, except that properties can be Pascal-cased while the parameters are camel-cased.
  • EF 核心無法設定 (例如部落格或上述文章) 的導覽屬性使用建構函式。EF Core cannot set navigation properties (such as Blog or Posts above) using a constructor.
  • 建構函式可以是公用、 私用,或有任何其他協助工具。The constructor can be public, private, or have any other accessibility.

唯讀屬性Read-only properties

一旦透過建構函式設定屬性之後將可以有意義將其中部分為唯讀。Once properties are being set via the constructor it can make sense to make some of them read-only. EF 核心支援,但要留意的一些事項:EF Core supports this, but there are some things to look out for:

  • 沒有 setter 的屬性未對應的慣例。Properties without setters are not mapped by convention. (這樣通常會以對應不應對應,例如計算屬性的屬性。)(Doing so tends to map properties that should not be mapped, such as computed properties.)
  • 使用自動產生索引鍵的值必須是讀寫,因為插入新的實體時,金鑰產生器所要設定需要的索引鍵值的索引鍵屬性。Using automatically generated key values requires a key property that is read-write, since the key value needs to be set by the key generator when inserting new entities.

避免這些作業的簡單方法是使用私用 setter。An easy way to avoid these things is to use private setters. 例如: For example:

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 核心看見為讀寫,也就是說,所有的屬性對應與之前,以及索引鍵可能仍會存放區產生的私用 setter 的屬性。EF Core sees a property with a private setter as read-write, which means that all properties are mapped as before and the key can still be store-generated.

使用私用 setter 的替代方式是將屬性實際上是唯讀,並新增 OnModelCreating 更明確的對應。An alternative to using private setters is to make properties really read-only and add more explicit mapping in OnModelCreating. 同樣地,某些屬性可以完全移除並取代欄位。Likewise, some properties can be removed completely and replaced with only fields. 例如,請考慮這些實體類型:For example, consider these entity types:

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 中的此設定:And this configuration in 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);
        });
}

必須注意的事項:Things to note:

  • 金鑰 「 屬性 」 現在是欄位。The key "property" is now a field. 它不是readonly欄位,以便可以使用存放區產生的索引鍵。It is not a readonly field so that store-generated keys can be used.
  • 其他屬性是唯讀屬性只在建構函式中設定。The other properties are read-only properties set only in the constructor.
  • 如果只有 EF 來設定或讀取資料庫的主索引鍵值,就不需要包含於建構函式。If the primary key value is only ever set by EF or read from the database, then there is no need to include it in the constructor. 這讓 「 屬性 」 的索引鍵保持為簡單欄位,並使其清除,它應該不明確設定時建立新的部落格或文章。This leaves the key "property" as a simple field and makes it clear that it should not be set explicitly when creating new blogs or posts.

注意

此程式碼會產生編譯器警告 '169' 表示永遠不會使用此欄位。This code will result in compiler warning '169' indicating that the field is never used. 因為事實上 EF 核心使用的欄位 extralinguistic 的方式可以忽略。This can be ignored since in reality EF Core is using the field in an extralinguistic manner.

插入服務Injecting services

EF 核心也可以將 「 服務 」 插入到之實體類型的建構函式。EF Core can also inject "services" into an entity type's constructor. 例如,下列可插入:For example, the following can be injected:

  • DbContext -目前的內容執行個體,這也可輸入為衍生的 DbContext 類型DbContext - the current context instance, which can also be typed as your derived DbContext type
  • ILazyLoader -消極式載入服務-請參閱消極式載入文件如需詳細資訊ILazyLoader - the lazy-loading service--see the lazy-loading documentation for more details
  • Action<object, string> -消極式載入委派-請參閱消極式載入文件如需詳細資訊Action<object, string> - a lazy-loading delegate--see the lazy-loading documentation for more details
  • IEntityType -此實體類型相關聯的 EF 核心中繼資料IEntityType - the EF Core metadata associated with this entity type

注意

為準,EF 核心 2.1 中,可插入 EF Core 的已知的服務。As of EF Core 2.1, only services known by EF Core can be injected. 用於將應用程式服務的支援是正在考慮未來的版本。Support for injecting application services is being considered for a future release.

例如,插入的 DbContext 可用來選擇性地存取資料庫,以取得相關實體的相關資訊,而不會載入所有它們。For example, an injected DbContext can be used to selectively access the database to obtain information about related entities without loading them all. 在下列範例中,這用來取得而不必載入張貼的部落格文章的數目:In the example below this is used to obtain the number of posts in a blog without loading the posts:

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

請注意相關的幾件事:A few things to notice about this:

  • 因為它永遠只會呼叫 EF 核心,而且沒有其他公用建構函式一般用途,是私人的建構函式。The constructor is private, since it is only ever called by EF Core, and there is another public constructor for general use.
  • 使用插入的服務的程式碼 (也就是內容) 會針對它防禦正在null來處理一些情況其中 EF 核心不會建立執行個體。The code using the injected service (i.e. the context) is defensive against it being null to handle cases where EF Core is not creating the instance.
  • 因為服務儲存在讀/寫屬性,如果實體附加到新的內容執行個體,它會被重設。Because service is stored in a read/write property it will be reset when the entity is attached to a new context instance.

警告

插入如下 DbContext 是通常會視為反面模式,因為直接到 EF Core 涉入實體類型。Injecting the DbContext like this is often considered an anti-pattern since it couples your entity types directly to EF Core. 使用像這樣的服務資料隱碼之前,請審慎考慮所有選項。Carefully consider all options before using service injection like this.