Entitätstypen mit Konstruktoren

Es ist möglich, einen Konstruktor mit Parametern zu definieren und EF Core beim Erstellen einer Instanz der Entität aufzurufen. Die Konstruktorparameter können an zugeordnete Eigenschaften oder an verschiedene Arten von Diensten gebunden werden, um Verhaltensweisen wie lazy-laden zu erleichtern.

Hinweis

Derzeit ist alle Konstruktorbindung konventionlich. Die Konfiguration bestimmter Konstruktoren, die verwendet werden sollen, ist für eine zukünftige Version geplant.

Bindung an zugeordnete Eigenschaften

Betrachten Sie ein typisches Blog/Post-Modell:

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

Wenn EF Core Instanzen dieser Typen erstellt, z. B. für die Ergebnisse einer Abfrage, ruft er zuerst den Standardparameterlosen Konstruktor auf und legt dann jede Eigenschaft auf den Wert aus der Datenbank fest. Wenn EF Core jedoch einen parameterisierten Konstruktor mit Parameternamen und Typen findet, die mit den zugeordneten Eigenschaften übereinstimmen, ruft er stattdessen den parameterisierten Konstruktor mit Werten für diese Eigenschaften auf und legt jede Eigenschaft nicht explizit fest. Beispiel:

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

Hinweise, die Sie beachten sollten:

  • Nicht alle Eigenschaften müssen Konstruktorparameter aufweisen. Die Post.Content-Eigenschaft wird beispielsweise nicht von einem Konstruktorparameter festgelegt, sodass EF Core sie nach dem Aufrufen des Konstruktors auf normale Weise festlegen wird.
  • Die Parametertypen und Namen müssen Mit Eigenschaftentypen und Namen übereinstimmen, außer dass Eigenschaften Pascal-cased sein können, während die Parameter kamel-cased sind.
  • EF Core kann keine Navigationseigenschaften (z. B. Blog oder Beiträge oben) mithilfe eines Konstruktors festlegen.
  • Der Konstruktor kann öffentlich, privat oder über eine andere Barrierefreiheit verfügen. Lazy-ladende Proxies erfordern jedoch, dass der Konstruktor aus der erbenden Proxyklasse zugänglich ist. Dies bedeutet in der Regel, dass sie entweder öffentlich oder geschützt ist.

Schreibgeschützte Eigenschaften

Sobald Eigenschaften über den Konstruktor festgelegt werden, kann es sinnvoll sein, einige dieser Eigenschaften schreibgeschützt zu machen. EF Core unterstützt dies, aber es gibt einige Dinge, die sie suchen:

  • Eigenschaften ohne Setter werden nicht durch Konvention zugeordnet. (Dadurch werden Eigenschaften zugeordnet, die nicht zugeordnet werden sollten, z. B. berechnete Eigenschaften.)
  • Die Verwendung automatisch generierter Schlüsselwerte erfordert eine Schlüsseleigenschaft, die schreibgeschützt ist, da der Schlüsselwert beim Einfügen neuer Entitäten vom Schlüsselgenerator festgelegt werden muss.

Eine einfache Möglichkeit, diese Dinge zu vermeiden, besteht darin, private Setter zu verwenden. Beispiel:

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 sieht eine Eigenschaft mit einem privaten Setter als Lesezugriff, was bedeutet, dass alle Eigenschaften wie zuvor zugeordnet werden, und der Schlüssel kann weiterhin gespeichert werden.

Eine Alternative zur Verwendung privater Setter besteht darin, Eigenschaften wirklich schreibgeschützt zu machen und in OnModelCreating explizitere Zuordnung hinzuzufügen. Ebenso können einige Eigenschaften vollständig entfernt und nur durch Felder ersetzt werden. Betrachten Sie beispielsweise die folgenden Entitätstypen:

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

Und diese Konfiguration 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);
        });
}

Hinweise:

  • Der Schlüssel "Eigenschaft" ist jetzt ein Feld. Es ist kein readonly Feld, sodass speichergenerierte Schlüssel verwendet werden können.
  • Die anderen Eigenschaften sind schreibgeschützte Eigenschaften, die nur im Konstruktor festgelegt sind.
  • Wenn der Primärschlüsselwert nur von EF festgelegt oder aus der Datenbank gelesen wird, muss er nicht in den Konstruktor einbezogen werden. Dies verlässt die Schlüsseleigenschaft als einfaches Feld und macht klar, dass sie beim Erstellen neuer Blogs oder Beiträge nicht explizit festgelegt werden sollte.

Hinweis

Dieser Code führt zu Einer Compilerwarnung "169", die angibt, dass das Feld nie verwendet wird. Dies kann ignoriert werden, da in Realität EF Core das Feld auf extralinguistische Weise verwendet.

Injizieren von Diensten

EF Core kann auch "Dienste" in den Konstruktor eines Entitätstyps einfügen. Beispielsweise kann folgendes eingefügt werden:

  • DbContext - die aktuelle Kontextinstanz, die auch als abgeleiteter DbContext-Typ eingegeben werden kann
  • ILazyLoader - der lazy-ladedienst--see the lazy-loading documentation for more details
  • Action<object, string> - eine lazy-ladende Stellvertretung--siehe die lazy-ladende Dokumentation , um weitere Details zu erhalten
  • IEntityType - die EF Core-Metadaten, die diesem Entitätstyp zugeordnet sind

Hinweis

Derzeit können nur dienste, die von EF Core bekannt sind, injiziert werden. Die Unterstützung für das Einfügen von Anwendungsdiensten wird für eine zukünftige Version berücksichtigt.

Beispielsweise kann ein injizierter DbContext verwendet werden, um selektiv auf die Datenbank zuzugreifen, um Informationen zu verwandten Entitäten abzurufen, ohne sie alle zu laden. Im folgenden Beispiel wird verwendet, um die Anzahl der Beiträge in einem Blog zu erhalten, ohne die Beiträge zu laden:

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

Einige Dinge, die sie beachten möchten:

  • Der Konstruktor ist privat, da es nur von EF Core aufgerufen wird, und es gibt einen anderen öffentlichen Konstruktor für die allgemeine Verwendung.
  • Der Code mit dem injizierten Dienst (das heißt, der Kontext) ist gegen die Behandlung von Fällen, null in denen EF Core die Instanz nicht erstellt.
  • Da der Dienst in einer Lese-/Schreibeigenschaft gespeichert wird, wird er zurückgesetzt, wenn die Entität an eine neue Kontextinstanz angefügt wird.

Warnung

Das Injizieren des DbContexts wie dies wird häufig als Antimuster betrachtet, da sie Ihre Entitätstypen direkt mit EF Core gekoppelt. Berücksichtigen Sie sorgfältig alle Optionen, bevor Sie die Diensteinführung wie folgt verwenden.