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

注意

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

從開始 EF Core 2.1,您就可以定義參數的建構函式,並已建立實體的執行個體時呼叫這個建構函式的 EF Core。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 Core 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 Core 建立這些類型的執行個體時,例如結果的查詢時,它會先呼叫預設的無參數建構函式,然後設定每一個屬性的值從資料庫。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 Core 找到的參數化建構函式參數名稱和相符的型別對應屬性,則它會改為呼叫這些屬性值的參數化建構函式,並將未明確設定每個屬性。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 Core 之後呼叫建構函式以一般方式將設定。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 Core 無法設定 (例如部落格或上述文章) 的導覽屬性使用建構函式。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 Core 支援,但要留意的一些事項: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 Core 看見為讀寫,也就是說,所有的屬性對應與之前,以及索引鍵可能仍會存放區產生的私用 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:

  • 索引鍵"property"現在是欄位。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 Core 使用的欄位 extralinguistic 的方式可以忽略。This can be ignored since in reality EF Core is using the field in an extralinguistic manner.

插入的服務Injecting services

EF Core 也可以將 「 服務 」 插入到之實體類型的建構函式。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 Core 中繼資料IEntityType - the EF Core metadata associated with this entity type

注意

為準,EF Core 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 Core,而且沒有其他公用建構函式一般用途,是私人的建構函式。The constructor is private, since it is only ever called by EF Core, and there is another public constructor for general use.
  • 使用插入的服務 (也就是內容) 的程式碼是針對它防禦性正在null處理其中 EF Core 不會建立執行個體的情況。The code using the injected service (that is, 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.